Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reward Boss implementation faithful to Tibia's one. #1628

Closed
wants to merge 19 commits into from
Closed

Reward Boss implementation faithful to Tibia's one. #1628

wants to merge 19 commits into from

Conversation

cezarguimaraes
Copy link
Contributor

Reasoning

After seeing a few reward systems on OTLand try and fail to replicate the original one, I decided to give it a try myself. Some images of the result:
Boss Loot Reward Chest Reward Chest 2

How I did it

Boss Corpses

The difference between a normal monster corpse and a corpse that should yield individual rewards to players that participated in the fight is its attributes. ITEM_ATTRIBUTE_CORPSEOWNER is set to 2^31-1 and ITEM_ATTRIBUTE_DATE stores the time it was killed.

New class: Reward

It's the Reward class responsibility to store the different rewards a player may receive and, just like a depot chest, its contents are stored into the database when the player logs out and again loaded when they log back.

New class: Reward Chest

Analogous to the Depot Locker, the Reward Chest contains the Reward (that inside the game is a reward container. However, it contains all the Rewards. They are inserted into the chest during the player load phase and every time another Reward is created via:

Player::getReward(uint32_t timestamp, bool autoCreate);
// or
Player.getReward(self, timestamp, autoCreate)

Specifics

Timestamps

Timestamps are set both on the boss corpse and on the in-game reward container / Reward object. They're used to identify the Reward and to limit how long the reward is stored in the database (Tibia chose a maximum of 7 days).

How is each player attributed a different corpsereward container?

The Player class now holds a rewardCorpses map that stores each corpse, which are instantiated the first time that the player opens the body and are collected in the player destructor (together with the player's Rewards). (Not anymore: 7db6344)
The boss now drops a proxy reward container, meaning it is not a Reward object, but simply an item with the same id. Whenever a player tries to open this container, he will actually open his own Reward, this way there is no need to store unique corpses objects for each player.

How is the loot handled?

Each player's contribution is measured by an onHealthChange event. The player will be considered active in the fight if he is currently in the boss' targetList - or whenever he heals someone that is currently active. The contribution is kept stored even if the player disconnects during the fight. If during the loot distribution phase the player is offline, he'll still receive his reward (Lua implementation: insertRewardBag(guid, timestamp, itemList)).

How to enable reward distribution to a boss

Set the *rewardboss flag to true on the monster .xml file:

<flag rewardboss="1" />

To make a loot unique and awarded to the highest scoring player:

<item id="5903" chance="100000" unique="1"/><!-- ferumbras' hat -->

And register the corresponding events by calling Monster.setReward(self, enable) whenever a boss is summoned (e.g place_monster.lua).

Player contribution

Player contribution is currently calculated like this:

local score = (playerDamageOut / totalDamageOut) + (playerDamageIn / totalDamageIn) + (player.Healing / totalHealing)
score = score / 3 -- normalize to 0-1
local expectedScore = 1 / participants
local lootFactor = 1
lootFactor = lootFactor / participants ^ (1 / 3) -- tone down the loot a notch if there are many participants
lootFactor = lootFactor * (1 + lootFactor) ^ (score / expectedScore) -- increase the loot multiplicatively by how many times the player surpassed the expected score

Each lootBlock is therefore lootFactor times more probable to be awarded to a specific player. However it is far from being optimal if we want to achieve a result similar to Tibia's and only with the community help we will manage that.

@kornholi kornholi added the enhancement Increase or improvement in quality, value, or extent label Dec 5, 2015

<event type="death" name="BossDeath" script="boss.lua" />
<event type="healthchange" name="BossParticipation" script="boss.lua" />
<event type="think" name="BossThink" script="boss.lua" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indendation on all the lines

@Healek
Copy link

Healek commented Dec 7, 2015

error in compilation, how to fix?

/theforgottenserver/src/actions.cpp: In member function 'ReturnValue Actions::internalUseItem(Player_, const Position&, uint8_t, Item_, bool)':
/theforgottenserver/src/actions.cpp:348:18: error: 'class Container' has no member named 'isRewardCorpse'
if (container->isRewardCorpse()) {
^
make[2]: *** [CMakeFiles/tfs.dir/src/actions.cpp.o] Error 1
make[1]: *** [CMakeFiles/tfs.dir/all] Error 2
make: *** [all] Error 2

@conde2
Copy link
Contributor

conde2 commented Dec 14, 2015

@socket2810

Just curious if there is any reason for closing this PR...

@cezarguimaraes
Copy link
Contributor Author

@conde2

I had made it in my master branch. Whenever I make any new changes I'll open another PR and reference this one.

@ranisalt
Copy link
Member

You could as well rename your branch locally, push it to github, then reset against this master...

@cezarguimaraes
Copy link
Contributor Author

@ranisalt
I hope you can forgive me if this was the only solution I could find with my limited time and git knowledge...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Increase or improvement in quality, value, or extent
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants