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

Improvements that the Bestiary needs. #4412

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions data/lib/core/bestiary.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
local bestiaryData = {
-- dummy tables for very rare and none
[-2] = {kills = {0, 0, 0}},
[-1] = {kills = {2, 3, 5}},

-- number of stars (from harmless to challenging)
-- rarity 4 triggers rare values
[0] = {kills = {5, 10, 25}, charmPoints = 1},
[1] = {kills = {10, 100, 250}, charmPoints = 5},
[2] = {kills = {25, 250, 500}, charmPoints = 15},
[3] = {kills = {50, 500, 1000}, charmPoints = 25},
[4] = {kills = {100, 1000, 2500}, charmPoints = 50},
[5] = {kills = {200, 2000, 5000}, charmPoints = 100}
}
Copy link
Member

@ranisalt ranisalt Apr 22, 2023

Choose a reason for hiding this comment

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

What if I want to create a monster with a custom amount of kills per step?

I'm OK with defaulting to Tibia behavior, but not as much with only being able to have Tibia behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if the one I posted above is enough.


function MonsterType:getBestiaryKills()
if self:raceId() == 0 then
return bestiaryData[-2].kills
end

local info = self:bestiaryInfo()
if info.occurrence == 3 then
return bestiaryData[-1].kills
end
return bestiaryData[info.difficulty].kills
end

function MonsterType:getBestiaryCharmPoints()
if self:raceId() == 0 then
return 0
end
local info = self:bestiaryInfo()
return bestiaryData[info.difficulty].charmPoints * (info.occurrence == 3 and 5 or 2)
end
1 change: 1 addition & 0 deletions data/lib/core/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dofile('data/lib/core/storages.lua')

dofile('data/lib/core/achievements.lua')
dofile('data/lib/core/actionids.lua')
dofile('data/lib/core/bestiary.lua')
dofile('data/lib/core/combat.lua')
dofile('data/lib/core/constants.lua')
dofile('data/lib/core/container.lua')
Expand Down
5 changes: 2 additions & 3 deletions data/lib/core/player.lua
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,8 @@ function Player.addBestiaryKills(self, raceId)

local kills = self:getBestiaryKills(raceId)
local newKills = kills + 1
local bestiaryInfo = monsterType:getBestiaryInfo()
for _, totalKills in pairs({bestiaryInfo.prowess, bestiaryInfo.expertise, bestiaryInfo.mastery}) do
if kills == 0 or (kills < totalKills and newKills >= totalKills) then
for _, amount in pairs(monsterType:getBestiaryKills()) do
if kills == 0 or (kills < amount and newKills >= amount) then
self:sendTextMessage(MESSAGE_EVENT_DEFAULT, string.format("You unlocked details for the creature %s.", monsterType:getName()))
self:sendBestiaryMilestoneReached(raceId)
break
Expand Down
6 changes: 1 addition & 5 deletions data/monster/lua/#example.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ monster.flags = {
monster.bestiary = {
class = "Dragon",
raceId = 39,
prowess = 50,
expertise = 500,
mastery = 1000,
charmPoints = 25,
difficulty = "medium", -- harmless, trivial, easy, medium, hard, challenging
occurrence = 0,
occurrence = "common", -- common, uncommon, rare, very rare
locations = "Example locations"
}

Expand Down
3 changes: 1 addition & 2 deletions data/monster/monsters/dragon_lord.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
<flag staticattack="80" />
<flag runonhealth="300" />
</flags>
<bestiary class="Dragon" prowess="50" expertise="500" mastery="1000"
charmPoints="25" difficulty="medium" occurrence="0" locations="Dragon Lord locations"/>
<bestiary class="Dragon" difficulty="medium" occurrence="common" locations="Dragon Lord locations"/>
<attacks>
<attack name="melee" interval="2000" min="0" max="-230" />
<attack name="fire" interval="2000" chance="20" range="7" radius="4" target="1" min="-100" max="-200">
Expand Down
2 changes: 1 addition & 1 deletion data/scripts/creaturescripts/bestiary_kills.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function creatureEvent.onKill(player, target)
return true
end

local raceId = monster:getType():getBestiaryInfo().raceId
local raceId = monster:getType():raceId()
if raceId == 0 then
return true
end
Expand Down
8 changes: 8 additions & 0 deletions data/scripts/lib/register_monster_type.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,17 @@ do
challenging = 5
}

local occurrences = {
common = 0,
uncommon = 1,
rare = 2,
["very rare"] = 3
}

registerMonsterType.bestiary = function(mtype, mask)
if mask.bestiary then
mask.bestiary.difficulty = difficulties[mask.bestiary.difficulty:lower()]
mask.bestiary.occurrence = occurrences[mask.bestiary.occurrence:lower()]
mtype:bestiaryInfo(mask.bestiary)
end
end
Expand Down
14 changes: 7 additions & 7 deletions data/scripts/network/bestiary_classes.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
local function getUnlockedBestiary(player, monsterTypes)
local count = 0
for _, monsterType in pairs(monsterTypes) do
local info = monsterType:getBestiaryInfo()
if player:getStorageValue(PlayerStorageKeys.bestiaryKillsBase + info.raceId) >= info.prowess then
count = count + 1
local unlocked = 0
for _, mType in pairs(monsterTypes) do
local amount = mType:getBestiaryKills()[1]
if player:getStorageValue(PlayerStorageKeys.bestiaryKillsBase + mType:raceId()) >= amount then
unlocked = unlocked + 1
end
end
return count
return unlocked
end

local handler = PacketHandler(0xE1)

function handler.onReceive(player)
local bestiaryClasses = Game.getBestiary()
local bestiaryClasses = Game.getBestiaryClasses()
local msg = NetworkMessage()
msg:addByte(0xD5)
msg:addU16(#bestiaryClasses)
Expand Down
42 changes: 17 additions & 25 deletions data/scripts/network/bestiary_info.lua
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
local function getLootDifficulty(chance)
if chance < 200 then
return 4
elseif chance < 1000 then
return 3
elseif chance < 5000 then
return 2
elseif chance < 25000 then
return 1
for i = 0, 4 do
if chance < 200 * 5 ^ i then
return 4 - i
end
end
return 0
end

local bestiaryElements = {
Expand Down Expand Up @@ -42,27 +37,24 @@ function handler.onReceive(player, msg)

local info = monsterType:getBestiaryInfo()
local kills = player:getBestiaryKills(raceId)
local progress = 0
local monsterKills = monsterType:getBestiaryKills()
local step = kills == 0 and 0 or 4
if kills ~= 0 then
progress = 4
for i, amount in pairs({info.prowess, info.expertise, info.mastery}) do
if kills < amount then
progress = i
break
end
for i, amount in pairs({0, unpack(monsterKills)}) do
step = kills >= amount and i or step
end
end

local response = NetworkMessage()
response:addByte(0xD7)
response:addU16(raceId)
response:addString(info.class)
response:addByte(progress)
response:addByte(step)
response:addU32(kills)

response:addU16(info.prowess)
response:addU16(info.expertise)
response:addU16(info.mastery)
for _, amount in pairs(monsterKills) do
response:addU16(amount)
end

response:addByte(info.difficulty)
response:addByte(info.occurrence)
Expand All @@ -72,7 +64,7 @@ function handler.onReceive(player, msg)

for _, lootItem in pairs(loot) do
local lootDifficulty = getLootDifficulty(lootItem.chance)
local knowLoot = lootDifficulty <= progress
local knowLoot = lootDifficulty <= step
local itemType = ItemType(lootItem.itemId)
response:addU16(knowLoot and itemType:getClientId() or 0)
response:addByte(lootDifficulty)
Expand All @@ -83,8 +75,8 @@ function handler.onReceive(player, msg)
end
end

if progress > 1 then
response:addU16(info.charmPoints)
if step > 1 then
response:addU16(monsterType:getBestiaryCharmPoints())
response:addByte(monsterType:isHostile() and 2 or 1)
response:addByte(2)
response:addU32(monsterType:getMaxHealth())
Expand All @@ -93,7 +85,7 @@ function handler.onReceive(player, msg)
response:addU16(monsterType:getArmor())
end

if progress > 2 then
if step > 2 then
local elements = getBestiaryElements(monsterType)
response:addByte(#elements)
for index, value in pairs(elements) do
Expand All @@ -105,7 +97,7 @@ function handler.onReceive(player, msg)
response:addString(info.locations)
end

if progress > 3 then
if step > 3 then
response:addByte(0)
response:addByte(1)
end
Expand Down
27 changes: 11 additions & 16 deletions data/scripts/network/bestiary_monster_types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ local handler = PacketHandler(0xE2)
function handler.onReceive(player, msg)
local monsterTypes = {}
local className = ""
if msg:getByte() == 1 then -- search
local amount = msg:getU16()
for i = 1, amount do
if msg:getByte() == 1 then -- Search Mode
for i = 1, msg:getU16() do
local raceId = msg:getU16()
local monsterType = MonsterType(raceId)
if monsterType and player:getBestiaryKills(raceId) > 0 then
Expand All @@ -14,8 +13,7 @@ function handler.onReceive(player, msg)
end
else
className = msg:getString()
local bestiaryClasses = Game.getBestiary()
for _, class in pairs(bestiaryClasses) do
for _, class in pairs(Game.getBestiaryClasses()) do
if class.name == className then
monsterTypes = class.monsterTypes
break
Expand All @@ -32,21 +30,18 @@ function handler.onReceive(player, msg)
response:addString(className)
response:addU16(#monsterTypes)

for _, monsterType in pairs(monsterTypes) do
local info = monsterType:getBestiaryInfo()
response:addU16(info.raceId)
local kills = player:getBestiaryKills(info.raceId)
for _, mType in pairs(monsterTypes) do
local raceId = mType:raceId()
response:addU16(raceId)
local kills = player:getBestiaryKills(raceId)
if kills == 0 then
response:addByte(0)
else
local progress = 4
for i, amount in pairs({info.prowess, info.expertise, info.mastery}) do
if kills < amount then
progress = i
break
end
local step = 0
for i, amount in pairs({0, unpack(mType:getBestiaryKills())}) do
step = kills >= amount and i or step
end
response:addU16(progress)
response:addU16(step)
end
end

Expand Down
50 changes: 29 additions & 21 deletions src/luascript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,16 +844,11 @@ Reflect LuaScriptInterface::getReflect(lua_State* L, int32_t arg)
BestiaryInfo LuaScriptInterface::getBestiaryInfo(lua_State* L, int32_t arg)
{
std::string className = getFieldString(L, arg, "class");
uint32_t raceId = getField<uint32_t>(L, arg, "raceId");
uint32_t prowess = getField<uint32_t>(L, arg, "prowess");
uint32_t expertise = getField<uint32_t>(L, arg, "expertise");
uint32_t mastery = getField<uint32_t>(L, arg, "mastery");
uint32_t charmPoints = getField<uint32_t>(L, arg, "charmPoints");
uint32_t difficulty = getField<uint32_t>(L, arg, "difficulty");
uint32_t occurrence = getField<uint32_t>(L, arg, "occurrence");
uint8_t difficulty = getField<uint8_t>(L, arg, "difficulty");
uint8_t occurrence = getField<uint8_t>(L, arg, "occurrence");
std::string locations = getFieldString(L, arg, "locations");
lua_pop(L, 9);
return {className, raceId, prowess, expertise, mastery, charmPoints, difficulty, occurrence, locations};
lua_pop(L, 4);
return {className, difficulty, occurrence, locations};
}

Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg)
Expand Down Expand Up @@ -1036,13 +1031,8 @@ void LuaScriptInterface::pushReflect(lua_State* L, const Reflect& reflect)

void LuaScriptInterface::pushBestiaryInfo(lua_State* L, const BestiaryInfo& info)
{
lua_createtable(L, 0, 9);
lua_createtable(L, 0, 4);
setField(L, "class", info.className);
setField(L, "raceId", info.raceId);
setField(L, "prowess", info.prowess);
setField(L, "expertise", info.expertise);
setField(L, "mastery", info.mastery);
setField(L, "charmPoints", info.charmPoints);
setField(L, "difficulty", info.difficulty);
setField(L, "occurrence", info.occurrence);
setField(L, "locations", info.locations);
Expand Down Expand Up @@ -2226,7 +2216,7 @@ void LuaScriptInterface::registerFunctions()
registerMethod("Game", "getPlayerCount", LuaScriptInterface::luaGameGetPlayerCount);
registerMethod("Game", "getNpcCount", LuaScriptInterface::luaGameGetNpcCount);
registerMethod("Game", "getMonsterTypes", LuaScriptInterface::luaGameGetMonsterTypes);
registerMethod("Game", "getBestiary", LuaScriptInterface::luaGameGetBestiary);
registerMethod("Game", "getBestiaryClasses", LuaScriptInterface::luaGameGetBestiaryClasses);
registerMethod("Game", "getCurrencyItems", LuaScriptInterface::luaGameGetCurrencyItems);
registerMethod("Game", "getItemTypeByClientId", LuaScriptInterface::luaGameGetItemTypeByClientId);
registerMethod("Game", "getMountIdByLookType", LuaScriptInterface::luaGameGetMountIdByLookType);
Expand Down Expand Up @@ -3056,6 +3046,7 @@ void LuaScriptInterface::registerFunctions()

registerMethod("MonsterType", "name", LuaScriptInterface::luaMonsterTypeName);
registerMethod("MonsterType", "nameDescription", LuaScriptInterface::luaMonsterTypeNameDescription);
registerMethod("MonsterType", "raceId", LuaScriptInterface::luaMonsterTypeRaceId);

registerMethod("MonsterType", "health", LuaScriptInterface::luaMonsterTypeHealth);
registerMethod("MonsterType", "maxHealth", LuaScriptInterface::luaMonsterTypeMaxHealth);
Expand Down Expand Up @@ -4556,17 +4547,17 @@ int LuaScriptInterface::luaGameGetMonsterTypes(lua_State* L)
return 1;
}

int LuaScriptInterface::luaGameGetBestiary(lua_State* L)
int LuaScriptInterface::luaGameGetBestiaryClasses(lua_State* L)
{
// Game.getBestiary()
lua_createtable(L, 0, g_monsters.bestiary.size());
// Game.getBestiaryClasses()
lua_createtable(L, g_monsters.bestiary.size(), 0);
int classIndex = 0;
for (const auto& [className, monsters] : g_monsters.bestiary) {
lua_createtable(L, 0, 2);
pushString(L, className);
lua_setfield(L, -2, "name");

lua_createtable(L, 0, monsters.size());
lua_createtable(L, monsters.size(), 0);
int index = 0;
for (const auto& monsterName : monsters) {
pushUserdata<const MonsterType>(L, g_monsters.getMonsterType(monsterName));
Expand Down Expand Up @@ -14207,6 +14198,23 @@ int LuaScriptInterface::luaMonsterTypeNameDescription(lua_State* L)
return 1;
}

int LuaScriptInterface::luaMonsterTypeRaceId(lua_State* L)
{
// get: monsterType:raceId() set: monsterType:raceId(id)
MonsterType* monsterType = getUserdata<MonsterType>(L, 1);
if (monsterType) {
if (lua_gettop(L) == 1) {
lua_pushnumber(L, monsterType->raceId);
} else {
monsterType->raceId = getNumber<uint32_t>(L, 2);
pushBoolean(L, true);
}
} else {
lua_pushnil(L);
}
return 1;
}

int LuaScriptInterface::luaMonsterTypeHealth(lua_State* L)
{
// get: monsterType:health() set: monsterType:health(health)
Expand Down Expand Up @@ -14998,7 +15006,7 @@ int LuaScriptInterface::luaMonsterTypeBestiaryInfo(lua_State* L)
pushBestiaryInfo(L, monsterType->bestiaryInfo);
} else if (isTable(L, 2)) {
auto info = getBestiaryInfo(L, 2);
if (g_monsters.isValidBestiaryInfo(info)) {
if (!info.className.empty()) {
monsterType->bestiaryInfo = std::move(info);
pushBoolean(L, g_monsters.addBestiaryMonsterType(monsterType));
} else {
Expand Down
Loading