• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Feature Reward Chest & Boss Reward [TFS 1.2]

I've used that one from github and i have error.
Code:
Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/reward_chest/boss.lua:onThink
data/creaturescripts/scripts/reward_chest/boss.lua:184: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
  [C]: ?
  [C]: in function 'pairs'
  data/creaturescripts/scripts/reward_chest/boss.lua:184: in function <data/creaturescripts/scripts/reward_chest/boss.lua:180>
data/creaturescripts/scripts/reward_chest/boss.lua
Code:
function onThink(creature, interval) -- 180 line
   local bossId = creature:getId()
   local info = globalBosses[bossId]
   -- Reset all players' status
   for _, player in pairs(info) do -- 184 line
     player.active = false -- 185 line
   end
data/lib/rewardboss.lua
Code:
if not globalBosses then
   globalBosses = {}
end

function Monster.setReward(self, enable)
   if enable then
     if not self:getType():isRewardBoss() then
       error("Rewards can only be enabled to rewards bosses.")
       return false
     end
     globalBosses[self:getId()] = {}
     self:registerEvent("BossDeath")
     self:registerEvent("BossThink")
   else
     globalBosses[self:getId()] = nil
     self:unregisterEvent("BossDeath")
     self:unregisterEvent("BossThink")
   end
   return true
end

local function pushValues(buffer, sep, ...)
   local argv = {...}
   local argc = #argv
   for k, v in ipairs(argv) do
     table.insert(buffer, v)
     if k < argc and sep then
       table.insert(buffer, sep)
     end
   end
end

function Item.getNameDescription(self)
   local subType = self:getSubType()
   local itemType = self:getType()

   local buffer = {}

   local name = self:getName() or ''
   if(#name ~= 0) then
     if(itemType:isStackable() and subType > 1) then
       pushValues(buffer, ' ', subType, self:getPluralName())
     else
       local article = self:getArticle() or ''
       pushValues(buffer, ' ', select(#article ~= 0 and 1 or 2, article, name))
     end
   else
     pushValues(buffer, ' ', 'an item of type', self:getId())
   end

   return table.concat(buffer)
end

function Container.getContentDescription(self, outputBuffer)
   local firstItem = true
   local buffer = outputBuffer or {}
   for i = 1, self:getSize() do
     local item = self:getItem(i - 1)

     if(firstItem) then
       firstItem = false
     else
       table.insert(buffer, ", ")
     end

     table.insert(buffer, item:getNameDescription())
   end

   if firstItem then
     table.insert(buffer, "nothing")
   end

   if not outputBuffer then
     return table.concat(buffer)
   end
end

function Player.getRewardChest(self, autocreate)
   return self:getDepotChest(99, autocreate)
end

function Player.inBossFight(self)
   if not next(globalBosses) then
     return false
   end
   local playerGuid = self:getGuid()

   for _, info in pairs(globalBosses) do
     local stats = info[playerGuid]
     if stats and stats.active then
       return stats
     end
   end
   return false
end

-- by https://otland.net/members/cbrm.25752/ with some modifications
function MonsterType.createLootItem(self, lootBlock, chance, lootTable)
   local lootTable, itemCount = lootTable or {}, 0
   local randvalue = math.random(0, 100000) / (getConfigInfo("rateLoot") * chance)
   if randvalue < lootBlock.chance then
     if (ItemType(lootBlock.itemId):isStackable()) then
       itemCount = randvalue % lootBlock.maxCount + 1
     else
       itemCount = 1
     end
   end

   while itemCount > 0 do
     local n = math.min(itemCount, 100)
     itemCount = itemCount - n
     table.insert(lootTable, {lootBlock.itemId, n})
   end

   return lootTable
end

function MonsterType.getBossReward(self, lootFactor, topScore)
   local result = {}
   if getConfigInfo("rateLoot") > 0 then
     for _, lootBlock in pairs(self:getLoot()) do
       if lootBlock.unique and not topScore then
         --continue
       else
         self:createLootItem(lootBlock, lootFactor, result)
       end
     end
   end
   return result
end
 
Last edited:
I've used that one from github and i have error.
Code:
Lua Script Error: [CreatureScript Interface]
data/creaturescripts/scripts/reward_chest/boss.lua:onThink
data/creaturescripts/scripts/reward_chest/boss.lua:184: bad argument #1 to 'pairs' (table expected, got nil)
stack traceback:
  [C]: ?
  [C]: in function 'pairs'
  data/creaturescripts/scripts/reward_chest/boss.lua:184: in function <data/creaturescripts/scripts/reward_chest/boss.lua:180>
data/creaturescripts/scripts/reward_chest/boss.lua
Code:
function onThink(creature, interval) -- 180 line
   local bossId = creature:getId()
   local info = globalBosses[bossId]
   -- Reset all players' status
   for _, player in pairs(info) do -- 184 line
     player.active = false -- 185 line
   end
data/lib/rewardboss.lua
Code:
if not globalBosses then
   globalBosses = {}
end

function Monster.setReward(self, enable)
   if enable then
     if not self:getType():isRewardBoss() then
       error("Rewards can only be enabled to rewards bosses.")
       return false
     end
     globalBosses[self:getId()] = {}
     self:registerEvent("BossDeath")
     self:registerEvent("BossThink")
   else
     globalBosses[self:getId()] = nil
     self:unregisterEvent("BossDeath")
     self:unregisterEvent("BossThink")
   end
   return true
end

local function pushValues(buffer, sep, ...)
   local argv = {...}
   local argc = #argv
   for k, v in ipairs(argv) do
     table.insert(buffer, v)
     if k < argc and sep then
       table.insert(buffer, sep)
     end
   end
end

function Item.getNameDescription(self)
   local subType = self:getSubType()
   local itemType = self:getType()

   local buffer = {}

   local name = self:getName() or ''
   if(#name ~= 0) then
     if(itemType:isStackable() and subType > 1) then
       pushValues(buffer, ' ', subType, self:getPluralName())
     else
       local article = self:getArticle() or ''
       pushValues(buffer, ' ', select(#article ~= 0 and 1 or 2, article, name))
     end
   else
     pushValues(buffer, ' ', 'an item of type', self:getId())
   end

   return table.concat(buffer)
end

function Container.getContentDescription(self, outputBuffer)
   local firstItem = true
   local buffer = outputBuffer or {}
   for i = 1, self:getSize() do
     local item = self:getItem(i - 1)

     if(firstItem) then
       firstItem = false
     else
       table.insert(buffer, ", ")
     end

     table.insert(buffer, item:getNameDescription())
   end

   if firstItem then
     table.insert(buffer, "nothing")
   end

   if not outputBuffer then
     return table.concat(buffer)
   end
end

function Player.getRewardChest(self, autocreate)
   return self:getDepotChest(99, autocreate)
end

function Player.inBossFight(self)
   if not next(globalBosses) then
     return false
   end
   local playerGuid = self:getGuid()

   for _, info in pairs(globalBosses) do
     local stats = info[playerGuid]
     if stats and stats.active then
       return stats
     end
   end
   return false
end

-- by https://otland.net/members/cbrm.25752/ with some modifications
function MonsterType.createLootItem(self, lootBlock, chance, lootTable)
   local lootTable, itemCount = lootTable or {}, 0
   local randvalue = math.random(0, 100000) / (getConfigInfo("rateLoot") * chance)
   if randvalue < lootBlock.chance then
     if (ItemType(lootBlock.itemId):isStackable()) then
       itemCount = randvalue % lootBlock.maxCount + 1
     else
       itemCount = 1
     end
   end

   while itemCount > 0 do
     local n = math.min(itemCount, 100)
     itemCount = itemCount - n
     table.insert(lootTable, {lootBlock.itemId, n})
   end

   return lootTable
end

function MonsterType.getBossReward(self, lootFactor, topScore)
   local result = {}
   if getConfigInfo("rateLoot") > 0 then
     for _, lootBlock in pairs(self:getLoot()) do
       if lootBlock.unique and not topScore then
         --continue
       else
         self:createLootItem(lootBlock, lootFactor, result)
       end
     end
   end
   return result
end

How did you do it? Did you clone the repository?
I recommend that you clone it if you didn't.

Anyway did you add the dofile in "data/lib/lib.lua"?
 
Last edited:
Yes i added dofile in lib.lua
Code:
dofile('data/lib/rewardboss.lua')

What exactly mean "Did you clone the repository?"? I don't understand "repository", sorry, please explain.

Compile was ok. Everything looks great but that error.
When i run server this error is spamming console.
I saw also my map loading 14 seconds instead of 9 seconds.

Is that reward chest system from github tested? Maybe it's not fully working or something..
 
Last edited:
Yes i added dofile in lib.lua
Code:
dofile('data/lib/rewardboss.lua')

What exactly mean "Did you clone the repository?"? I don't understand "repository", sorry, please explain.

Compile was ok. Everything looks great but that error.
When i run server this error is spamming console.
I saw also my map loading 14 seconds instead of 9 seconds.

Is that reward chest system from github tested? Maybe it's not fully working or something..

Execute:
Code:
git clone https://github.com/socket2810/forgottenserver.git -b reward_system /somedirectory

I did test, otherwise I would not recommend it. You probably forgot to add some change and that is causing the error, when I use the cloned data pack there are no errors.
 
First, thanks for sharing your code! @MatheusMkalo

I added this to your original creaturescript to prevent the crash:
Code:
local killer = Creature(creature)
if #killer:getSummons() > 0 then
    return true
end

I know it's not the best solution but for my server there really is no need for anyone to have a summon around while killing a boss, maybe it will help someone who is unable to switch.

EDIT:
I actually cancelled that and I'm using onHealthChange instead to cancel any damage from summons:

Code:
function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if (Monster(creature) ~= nil) then
        local mt = MonsterType(creature:getName())
        if mt:useRewardChest() then
            if attacker:getMaster() or not attacker:isPlayer() then
                return false
            end
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end
 
Last edited:
#up
Thank you very much. I will test your script soon and write a comment.

Execute:
Code:
git clone https://github.com/socket2810/forgottenserver.git -b reward_system /somedirectory

I did test, otherwise I would not recommend it. You probably forgot to add some change and that is causing the error, when I use the cloned data pack there are no errors.
I am using Windows 7.
I did spend a lot of time to make sure that everything i did good.
Where should i use this command? In this program from github?

Btw. I've tryed to update theforgotten sources to lastest but there was error while compiling "couldn't load items.h" or something like that so i decided to stay with my tfs 1.2 (it was updated to lastest sources maybe 3 months ago).
 
First, thanks for sharing your code! @MatheusMkalo

I added this to your original creaturescript to prevent the crash:
Code:
local killer = Creature(creature)
if #killer:getSummons() > 0 then
    return true
end

I know it's not the best solution but for my server there really is no need for anyone to have a summon around while killing a boss, maybe it will help someone who is unable to switch.

EDIT:
I actually cancelled that and I'm using onHealthChange instead to cancel any damage from summons:

Code:
function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if (Monster(creature) ~= nil) then
        local mt = MonsterType(creature:getName())
        if mt:useRewardChest() then
            if attacker:getMaster() or not attacker:isPlayer() then
                return false
            end
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end



Hi, thanks, but where we apply this?
I dont find onHealthChange on main post
 
creaturescripts.xml
Code:
<event type="healthChange" name="RewardHealth" script="reward_health_change.lua"/>

creaturescripts/scripts/reward_health_change.lua
Code:
function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if (Monster(creature) ~= nil) then
        local mt = MonsterType(creature:getName())
        if mt:useRewardChest() then
            if primaryType ~= COMBAT_HEALING then
                if attacker == nil then
                    return false
                end
                if attacker:getMaster() or not attacker:isPlayer() then
                    return false
                end
            end
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

Go to your monster file and add in these lines to the bosses you wish to use: [or you can use onTargetCombat/onAreaCombat to register it to the monsters]
Code:
  <script>
     <event name="RewardHealth"/>
   </script>
 
creaturescripts.xml
Code:
<event type="healthChange" name="RewardHealth" script="reward_health_change.lua"/>

creaturescripts/scripts/reward_health_change.lua
Code:
function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if (Monster(creature) ~= nil) then
        local mt = MonsterType(creature:getName())
        if mt:useRewardChest() then
            if primaryType ~= COMBAT_HEALING then
                if attacker == nil then
                    return false
                end
                if attacker:getMaster() or not attacker:isPlayer() then
                    return false
                end
            end
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

Go to your monster file and add in these lines to the bosses you wish to use: [or you can use onTargetCombat/onAreaCombat to register it to the monsters]
Code:
  <script>
     <event name="RewardHealth"/>
   </script>

Thanks man ! Amazing, We need apply this for register Event onLogin
player:registerEvent("RewardHealth")?? And this works 100%, this is all or missing some files?
 
Last edited by a moderator:
Thanks man ! Amazing, We need apply this for register Event onLogin
player:registerEvent("RewardHealth")?? And this works 100%, this is all or missing some files?

Since this script is meant to detect the health change of the monster you do not need to register it on login.lua but instead in the monster file as shown in the previous thread.
 
creaturescripts.xml
Code:
<event type="healthChange" name="RewardHealth" script="reward_health_change.lua"/>

creaturescripts/scripts/reward_health_change.lua
Code:
function onHealthChange(creature, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if (Monster(creature) ~= nil) then
        local mt = MonsterType(creature:getName())
        if mt:useRewardChest() then
            if primaryType ~= COMBAT_HEALING then
                if attacker == nil then
                    return false
                end
                if attacker:getMaster() or not attacker:isPlayer() then
                    return false
                end
            end
        end
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

Go to your monster file and add in these lines to the bosses you wish to use: [or you can use onTargetCombat/onAreaCombat to register it to the monsters]
Code:
  <script>
     <event name="RewardHealth"/>
   </script>
Never tried it personally so not sure if it would work, but do you think you could in fact change attacker to the master instead of returning false? That way the master gets the damage still but the summon doesn't break everything. Just a random thought
 
CDsTFk.png


Guys , i need some help!!

-----EDIT----
Nvm.... Thanks
 
Last edited:
Im having the following issue after trying to compile

Code:
/home/global/sources/src/actions.cpp:270:13: error: prototype for ‘ReturnValue Actions::internalUseItem(Player*, const Position&, uint32_t, Item*, bool)’ does not match any in class ‘Actions’
ReturnValue Actions::internalUseItem(Player* player, const Position& pos, uint32_t index, Item* item, bool isHot
  ^
In file included from /home/global/sources/src/actions.cpp:22:0:
/home/global/sources/src/actions.h:102:15: error: candidate is: ReturnValue Actions::internalUseItem(Player*, const Position&, uint8_t, Item*, bool)
  ReturnValue internalUseItem(Player* player, const Position& pos, uint8_t index, Item* item, bool isHotkey);
 
Looks like some MonsterType functions changed and you have to add "info." before rewardChest to make this work. Testing on TFS 1.3
 
Last edited:
Ok, just to help people with TFS 1.3

in src/depotchest.cpp:
Change
Code:
Container(type), maxDepotItems(1500) {}
TFS 1.2 was maxDepotItems = 1500;

For
Code:
Container(type), maxDepotItems(1500), depotId(0) {}

in src/monsters.h
Below...
Code:
bool hiddenHealth = false

...Add
Code:
bool rewardChest = false;

and also ignore this http://i.imgur.com/Pp8t5rM.jpg change

in src/monsters.cpp
Below...
Code:
           } else if (strcasecmp(attrName, "hidehealth") == 0) {

               mType->info.hiddenHealth = attr.as_bool();

...Add
Code:
           } else if (strcasecmp(attrName, "rewardchest") == 0) {
               mType->info.rewardChest = attr.as_bool();
(mType->rewardChest now is mType->info.rewardChest)

after your changes in src/luascript.cpp
Back there and change:
Code:
 pushBoolean(L, monsterType->rewardChest);

For:
Code:
  pushBoolean(L, monsterType->info.rewardChest);

Change:
Code:
parseLoot(monsterType->lootItems);

For:
Code:
parseLoot(monsterType->info.lootItems);

Everything is working fine here, with the TFS 1.3 master :)
 
[TFS 1.3] Works perfect thanks: grilo13, Mkalo
*Edit:
Crash when some1 receive too much items from 1boss (Gaz'haragoth)
 
Last edited:
Back
Top