• 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!

[7.7] RealOTS 7.7 Cipsoft files (virgin)

Hello guys!!
I have been a sick mystery-seeking madman for a short time now. Can someone here give me a hand with the Remeres map editor?
I have seen that .otbm files are needed but the original maps only contain the .sec files.
I don't know if it's better to transform them to the expected format, or to get an editor that reads the .sec files.
Can someone talk to me privately or help me with this?
I want to see and understand the maps better, I would even like to be able to open my own local server to check!
Thanks in advance!
 
Can anyone explain how SS works? When I send a kill -15 on the game's pid it doesn't clean the floor. The right thing to do would be to clean the entire floor except the houses, right?
 
Can anyone explain how SS works? When I send a kill -15 on the game's pid it doesn't clean the floor. The right thing to do would be to clean the entire floor except the houses, right?
It can either skip saving the map, or save the map with refreshing, or save the map without refreshing - depending on how you close it. Refreshing applies ONLY to all the tiles with refresh flag.
By default, the 'normal' daily reboot is done with saving and refreshing. If you close the server via signal 3, 15 or 30 it saves the map without refreshing, and if you do so via signal 2 (ctrl+c) it does not save the map at all. Signal 15 closes the server with 6 min delay and the red messages appear prior to that, the others should close it instantly.
 
Last edited:
It can either skip saving the map, or save the map with refreshing, or save the map without refreshing - depending on how you close it. Refreshing applies ONLY to all the tiles with refresh flag.
By default, the 'normal' daily reboot is done with saving and refreshing. If you close the server via signal 3, 15 or 30 it saves the map without refreshing, and if you do so via signal 2 (ctrl+c) it does not save the map at all. Signal 15 closes the server with 6 min delay and the red messages appear prior to that, the others should close it instantly.
how can I daily reboot?
 
how can I daily reboot?
Reboot is set true by default. You're passing RebootTime upon the launch via QueryManager, and the game server will shutdown at that time with saving and refreshing the map. Whether it can restart automatically depends on the script file "reboot-daily" that has to be placed in the same directory.
 
Reboot is set true by default. You're passing RebootTime upon the launch via QueryManager, and the game server will shutdown at that time with saving and refreshing the map. Whether it can restart automatically depends on the script file "reboot-daily" that has to be placed in the same directory.
thx for the reply. my server is online at zanera.servegame.com xD
 
Here's a pre-7.6-ish game.c in case anyone wants it. I say ish because I've only changed the magic system. Buying spells still require levels, but they've all been lowered to require level 1.
All changes, including the bytes, their values, and what those values mean are listed in the included txt-file.

Example:
Code:
Sudden Death:
 0x4416D - 0x4416E: 70 03 -> DC 00 // mana: 880 -> 220
 0x44175:           2D    -> 01    // lvl: 45 -> 1
 0x44195:           05    -> 00    // sp: 5 -> 0
Hi, thanks for sharing this. I've been digging around the game binary with a disassembler, and managed to try some of the changes you've shown.

I'd also like to modify the damage of some spells and runes, and increase the amount of mana/hp restored by mana/life fluids.

For the damage part, my guess is that I have to modify the min/max multipliers somehow (assuming this is correct: Real spell formulas from leaked files · Ezzz-dev/OTHire@43d3b1f (https://github.com/Ezzz-dev/OTHire/commit/43d3b1f3c83675451756d3e20064f1476e3ac7d0)). But I cannot figure out how you found the correct addresses. How did you know that, in your example, 0x4416D - 0x4416E was the values (880) for SD mana? Say I want to increase the min/max for UH runes, which is 250. In hex that is FA. Searching for FA results in a lot of hits... So how can I know which one is correct? Same goes for mana and life fluids.
 
@kay
Do you know why and could explain in simplified language why this happens: monsters double spellcasting when monster has no available targets -> next "onThink" has a target that is <=1 step away from melee range?
Here's an example: 0:39
I also made a short video dedicated to just exploring this

It's like it goes through onThink twice with these exact conditions, except that I'm not sure/I don't think it actually casts all the spells even when spellchance = 1, because it's rare, with my encounters with "hydrachad" like in my video above, that I'm hit 4 times by spells even though it has two spells, usually I'm only hit by 3 spells.

Mystery dominando explainando plz?
 
Hi, thanks for sharing this. I've been digging around the game binary with a disassembler, and managed to try some of the changes you've shown.

I'd also like to modify the damage of some spells and runes, and increase the amount of mana/hp restored by mana/life fluids.

For the damage part, my guess is that I have to modify the min/max multipliers somehow (assuming this is correct: Real spell formulas from leaked files · Ezzz-dev/OTHire@43d3b1f (https://github.com/Ezzz-dev/OTHire/commit/43d3b1f3c83675451756d3e20064f1476e3ac7d0)). But I cannot figure out how you found the correct addresses. How did you know that, in your example, 0x4416D - 0x4416E was the values (880) for SD mana? Say I want to increase the min/max for UH runes, which is 250. In hex that is FA. Searching for FA results in a lot of hits... So how can I know which one is correct? Same goes for mana and life fluids.
Don't quote me on this. But when I used to look for these things, all the spells and runes are in the same memory region, and every spell/rune has data for it's spell words. So you could look for those, in the nearby region of a known spell. But the thing is, its not a raw string. If i remember correctly there were parts like "ex", "ura", "ani", "utevo", "vita" etc, and you had to document which hex/ptr was what. Was a very long time ago I did this, so I might be far off. But I do remember the spell words werent just strings in there. Look and compare against known values to figure out which ptr means what.


EDIT: found some of my old notes looking at a spell in IDA (decompiled code).
With IDA and the decompiler you can just decompile, highlight a row and you can have the decompiled view sync with the hex view. Thus you can find it more easily. (Not always perfect)


LUA:
    eax115 = _Z11CreateSpelliz(42, "ex", "evo", "pan", 0x80fed0c, 0x80fed0c, 0x80fed0c, v114);
    eax115->f24 = 0x78;
    eax115->f16 = 14;
    eax115->f28 = 1;
    eax115->f20 = 0;
    eax115->f12 = reinterpret_cast<int32_t>("Food");
 
    f24 = Manacost
    f16 = Level-requirement (NON HEX)
    f18 = Maglvl to use (runes)
    f32 = Charges (runes)
    f20 = Type/Group ??
    f28 = Soulpoints


Some other notes, may be inaccurate. Multiplier stuff (skills/cap/mana/hp etc)

Code:
/*
VOCATION SKILL MULTIPLIERS
Knights: 1.1
Paladins: 1.2
Druids: 1.8
Sorcerers and Rookies: 2

VOCATION MAGICLEVEL MULTIPLIER
Mages: 1.1
Paladins: 1.4
Knights: 3
*/


void __usercall TPlayer::SetProfession(TPlayer *const this@<ebx>, char *a2@<eax>, int prof, unsigned __int8 a4)
{
  int profession; // ebx@1
  int *v5; // eax@8
  int v6; // edx@8
  int *v7; // eax@12
  int v8; // edx@12
  char *v9; // eax@17
  int v10; // eax@19
  signed int v11; // [sp+4h] [bp-24h]@8
  signed int v12; // [sp+4h] [bp-24h]@12
  const char *v13; // [sp+30h] [bp+8h]@6
  TCombat *v14; // [sp+30h] [bp+8h]@22
  int v15; // [sp+34h] [bp+Ch]@6

  profession = prof;
  if ( a4 == 10 )
  {
    v9 = (char *)*(_BYTE *)(prof + 790);
    if ( (_BYTE)v9 )
    {
      if ( (unsigned __int8)v9 <= 9u )
      {
        *(_BYTE *)(prof + 790) = (_BYTE)v9 + 10;
        TCombat::CheckCombatValues((TCombat *const )prof);
        *(_DWORD *)(*(_DWORD *)(prof + 92) + 24) = 200;
        v10 = (*(int (__cdecl **)(_DWORD))(**(_DWORD **)(prof + 92) + 52))(*(_DWORD *)(prof + 92));
        if ( v10 > 14 )
          TSkillBase::SetTimer((TSkillBase *const )(prof + 4), 0x16u, (v10 - 1) / 15 + 1, (v10 - 1) % 15 + 1, 15, -1);
        return;
      }
      v14 = (TCombat *)"TPlayer::SetProfession: Spieler hat seinen Beruf schon veredelt.\n";
    }
    else
    {
      v14 = (TCombat *)&unk_8104220;
    }
    error(v9, v14);
    return;
  }
  if ( *(_BYTE *)(prof + 790) )
  {
    v13 = "TPlayer::SetProfession: Player '%s' hat bereits einen Beruf!\n";
    v15 = profession + 532;
    goto LABEL_7;
  }
  a2 = (char *)a4;
  if ( a4 == 2 )
  {
      //SKILLRATES FOR PALADIN
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 36) + 28))(
      *(_DWORD *)(prof + 36),
      1200, //VOC SKILL MULTIPLIER
      50); //RATE
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 44) + 28))(
      *(_DWORD *)(prof + 44),
      1200,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 40) + 28))(
      *(_DWORD *)(prof + 40),
      1200,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 48) + 28))(
      *(_DWORD *)(prof + 48),
      1200,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 32) + 28))(
      *(_DWORD *)(prof + 32),
      1100,
      30);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 28) + 28))(
      *(_DWORD *)(prof + 28),
      1100,
      100);
    *(_DWORD *)(*(_DWORD *)(prof + 12) + 68) = 10; //Hp per level (Paladin)
    *(_DWORD *)(*(_DWORD *)(prof + 24) + 68) = 20; //Cap per level (Paladin)
    *(_DWORD *)(*(_DWORD *)(prof + 16) + 68) = 15; //MP per level (Paladin)
    v5 = *(int **)(prof + 8);
    v6 = *v5;
    v11 = 1400; //MANA REQUIREMENT MULTIPLIER TO ADVANCE
  }
  else if ( (signed int)a4 > 2 )
  {
    if ( a4 == 3 )
    {
      //SKILLRATES FOR SORC & NOVOC
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 36) + 28))(
        *(_DWORD *)(prof + 36),
        2000,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 44) + 28))(
        *(_DWORD *)(prof + 44),
        2000,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 40) + 28))(
        *(_DWORD *)(prof + 40),
        2000,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 48) + 28))(
        *(_DWORD *)(prof + 48),
        1500,
        50);
      v7 = *(int **)(prof + 32);
      v8 = *v7;
      v12 = 2000; //VOC SKILL MULTIPLIER FOR THE UNSPECIFIED?
    }
    else
    {
        //SKILLRATES FOR DRUID
      if ( a4 != 4 )
        goto LABEL_6;
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 36) + 28))(
        *(_DWORD *)(prof + 36),
        1800,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 44) + 28))(
        *(_DWORD *)(prof + 44),
        1800,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 40) + 28))(
        *(_DWORD *)(prof + 40),
        1800,
        50);
      (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 48) + 28))(
        *(_DWORD *)(prof + 48),
        1500,
        50);
      v7 = *(int **)(prof + 32);
      v8 = *v7;
      v12 = 1800; //VOC SKILL MULTIPLIER FOR THE UNSPECIFIED?
    }
    (*(void (__cdecl **)(int *, signed int, signed int))(v8 + 28))(v7, v12, 30);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 28) + 28))(
      *(_DWORD *)(prof + 28),
      1500,
      100);
    *(_DWORD *)(*(_DWORD *)(prof + 12) + 68) = 5; //HP per level (Sorc & Druid & NoVoc)
    *(_DWORD *)(*(_DWORD *)(prof + 24) + 68) = 10; //Cap per level
    *(_DWORD *)(*(_DWORD *)(prof + 16) + 68) = 30; //Mana per level
    v5 = *(int **)(prof + 8);
    v6 = *v5;
    v11 = 1100; //MANA REQUIREMENT MULTIPLIER TO ADVANCE MLVL (MAGES SORC & DRUID)
  }
  else
  {
    a2 = (char *)(a4 - 1);
    if ( a4 != 1 )
    {
LABEL_6:
      v13 = "TPlayer::SetProfession: Beruf %d existiert nicht!\n";
      v15 = a4;
LABEL_7:
      error(a2, v13, v15);
      return;
    }
    //SKILLRATES FOR KNIGHT
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 36) + 28))(
      *(_DWORD *)(prof + 36),
      1100,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 44) + 28))(
      *(_DWORD *)(prof + 44),
      1100,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 40) + 28))(
      *(_DWORD *)(prof + 40),
      1100,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 48) + 28))(
      *(_DWORD *)(prof + 48),
      1100,
      50);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 32) + 28))(
      *(_DWORD *)(prof + 32),
      1400,
      30);
    (*(void (__cdecl **)(_DWORD, signed int, signed int))(**(_DWORD **)(prof + 28) + 28))(
      *(_DWORD *)(prof + 28),
      1100,
      100);
    *(_DWORD *)(*(_DWORD *)(prof + 12) + 68) = 15; // Hp per level (KNIGHT)
    *(_DWORD *)(*(_DWORD *)(prof + 24) + 68) = 25; // Cap per level
    *(_DWORD *)(*(_DWORD *)(prof + 16) + 68) = 5; // Mana per level
    v5 = *(int **)(prof + 8);
    v6 = *v5;
    v11 = 3000; //MANA REQUIREMENT MULTIPLIER TO ADVANCE MLVL
  }
  (*(void (__cdecl **)(int *, signed int, signed int))(v6 + 28))(v5, v11, 1600);
  *(_BYTE *)(prof + 790) = a4;
  TCombat::CheckCombatValues((TCombat *const )prof);
  DecrementNewbiesOnline();
}


SecsPerHP is probably "v3"

Code:
void __cdecl TSkillFed::Event(int a2)
{
  RIGHT v1; // ebx@1
  int v2; // eax@5
  signed int v3; // edi@5
  int v4; // ebx@12
  int v5; // eax@12
  int v6; // edi@14
  int v7; // eax@14
  int v8; // [sp+4h] [bp-24h]@4
  int SecsPerMana; // [sp+18h] [bp-10h]@5

  v1 = *(_DWORD *)(a2 + 16);
  if ( v1 )
  {
    if ( !*(_BYTE *)(v1 + 656)
      && !(unsigned __int8)IsProtectionZone(*(_DWORD *)(v1 + 624), *(_DWORD *)(v1 + 628), *(_DWORD *)(v1 + 632)) )
    {
      SecsPerMana = 6;
      v2 = 0;
      v3 = 12;
      if ( !*(_DWORD *)(v1 + 652) )
        v2 = (unsigned __int8)TPlayer::GetActiveProfession(v1);
      switch ( v2 )
      {
        case 0:
        case 1:
          v3 = 6;
          goto LABEL_9;
        case 2:
          SecsPerMana = 4;
          v3 = 8;
          break;
        case 3:
        case 4:
          v3 = 12;
          goto LABEL_18;
        default:
          v8 = v2;
          error();
          break;
        case 11:
          v3 = 4;
LABEL_9:
          SecsPerMana = 6;
          break;
        case 12:
          v3 = 6;
LABEL_18:
          SecsPerMana = 3;
          break;
        case 13:
        case 14:
          SecsPerMana = 2;
          v3 = 12;
          break;
      }
      if ( !((*(int (__cdecl **)(int, int))(*(_DWORD *)a2 + 52))(a2, v8) % v3) )
      {
        v6 = *(_DWORD *)(v1 + 12);
        (*(void (__cdecl **)(_DWORD, int))(*(_DWORD *)v6 + 8))(*(_DWORD *)(v1 + 12), *(_DWORD *)(v6 + 20) + 1);
        v7 = *(_DWORD *)(v6 + 24);
        if ( *(_DWORD *)(v6 + 20) > v7 )
          *(_DWORD *)(v6 + 20) = v7;
      }
      if ( !((*(int (__cdecl **)(int))(*(_DWORD *)a2 + 52))(a2) % SecsPerMana) )
      {
        v4 = *(_DWORD *)(v1 + 16);
        (*(void (__cdecl **)(int, int))(*(_DWORD *)v4 + 8))(v4, *(_DWORD *)(v4 + 20) + 2);
        v5 = *(_DWORD *)(v4 + 24);
        if ( *(_DWORD *)(v4 + 20) > v5 )
          *(_DWORD *)(v4 + 20) = v5;
      }
    }
  }
  else
  {
    error();
  }
}
 
Last edited:
I'm trying to figure out the "true" formulas for attack, defense, and armor. When looking into the GitHub - Ezzz-dev/Nostalrius: Nostalrius is a 7.7 Tibia Clone Project based on The Forgotten Server 1.2 and CipSoft files. (https://github.com/Ezzz-dev/Nostalrius/tree/master), on the README file it says "(compromised myself into getting the true formulas of the game server)" I found on the src files, particle in player.cpp a function called

Player::getDefense() which has the formula to be Nostalrius/src/player.cpp at 3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3 · Ezzz-dev/Nostalrius (https://github.com/Ezzz-dev/Nostalrius/blob/3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3/src/player.cpp#L237)

I also found a function called Player::getArmor() at Nostalrius/src/player.cpp at 3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3 · Ezzz-dev/Nostalrius (https://github.com/Ezzz-dev/Nostalrius/blob/3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3/src/player.cpp#L188)


Going through the different source codes I came across and found that there is a Monster::getDefense() as well which can be found here:

Could anyone, perhaps @kay confirm if they are correct? Do monsters have their own attack formula, defense formula, armor formula, or is everything the same as the player?

I'm still trying to find the attack function with the attack formula.
 
Last edited:
Could anyone, perhaps @kay confirm if they are correct? Do monsters have their own attack formula, defense formula, armor formula, or is everything the same as the player?
I don't have time to browse through the whole code, but from a quick glance - they seem to be correct.
Armor reduction was: arm/2 + rand()%(arm/2). Mind, that this is all integers, so any odd arm value gives the same results as arm-1.
For damage or defending it is: (5 * skill + 50) * value * (rand()%100 + rand()%100)/2 / 10000.
No, monsters didn't have their own formulas. The reason why there are different functions for players and monsters in the linked sources is probably that Ezzz used tfs sources as a base. But if you actually look at the code, it is the same.
In real tibia there were only 2 formulas for all the skill-related stuff and for all creatures, which were: Probe (used to determine success/fail events, such as hitchance, catching a fish, breaking a stone with a pick etc.) and ProbeValue (used to determine the output value, such as damage or defending).
 
Last edited:
I'm trying to figure out the "true" formulas for attack, defense, and armor. When looking into the GitHub - Ezzz-dev/Nostalrius: Nostalrius is a 7.7 Tibia Clone Project based on The Forgotten Server 1.2 and CipSoft files. (https://github.com/Ezzz-dev/Nostalrius/tree/master), on the README file it says "(compromised myself into getting the true formulas of the game server)" I found on the src files, particle in player.cpp a function called

Player::getDefense() which has the formula to be Nostalrius/src/player.cpp at 3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3 · Ezzz-dev/Nostalrius (https://github.com/Ezzz-dev/Nostalrius/blob/3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3/src/player.cpp#L237)

I also found a function called Player::getArmor() at Nostalrius/src/player.cpp at 3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3 · Ezzz-dev/Nostalrius (https://github.com/Ezzz-dev/Nostalrius/blob/3c0a34e91e2afb17cbd85a75f5ab96171cd7d8d3/src/player.cpp#L188)


Going through the different source codes I came across and found that there is a Monster::getDefense() as well which can be found here:

Could anyone, perhaps @kay confirm if they are correct? Do monsters have their own attack formula, defense formula, armor formula, or is everything the same as the player?

I'm still trying to find the attack function with the attack formula.
You would also want to check if your distro applies the correct order overall for taking damage:

1. Shield, if no shield then weapon defend value (but not both at the same time). If there isn't shield nor weapon available then the Defend value in the .mon files is used, and then ProbeValue with fist fighting skill.
2. Then comes any amulets or rings like protection amulet, might ring, etc. And then armor which is the sum of all inventory items arm value plus the armor value at the .mon file.
3. Then armorValue = (armorValue/2)+ rand() % (armorValue/2);
 
I don't have time to browse through the whole code, but from a quick glance - they seem to be correct.
Armor reduction was: arm/2 + rand()%(arm/2). Mind, that this is all integers, so any odd arm value gives the same results as arm-1.
For damage or defending it is: (5 * skill + 50) * value * (rand()%100 + rand()%100)/2 / 10000.
No, monsters didn't have their own formulas. The reason why there are different functions for players and monsters in the linked sources is probably that Ezzz used tfs sources as a base. But if you actually look at the code, it is the same.
In real tibia there were only 2 formulas for all the skill-related stuff and for all creatures, which were: Probe (used to determine success/fail events, such as hitchance, catching a fish, breaking a stone with a pick etc.) and ProbeValue (used to determine the output value, such as damage or defending).
You would also want to check if your distro applies the correct order overall for taking damage:

1. Shield, if no shield then weapon defend value (but not both at the same time). If there isn't shield nor weapon available then the Defend value in the .mon files is used, and then ProbeValue with fist fighting skill.
2. Then comes any amulets or rings like protection amulet, might ring, etc. And then armor which is the sum of all inventory items arm value plus the armor value at the .mon file.
3. Then armorValue = (armorValue/2)+ rand() % (armorValue/2);

Thank you both for that!

I was able to adjust the getArmor function correctly. However, I have some issues with the defense formula. Currently, it looks as such:

I'm dying to fast, I compared with @kay server Tibiantis, and let myself get hit by a rat with a jacket, on my server I die much faster than on kays.

C++:
int32_t Player::getDefense() const
{
    int32_t defenseSkill = getSkillLevel(SKILL_FIST);
    int32_t totalDefense = 5; // Base defense value
    const Item* weapon;
    const Item* shield;
    getShieldAndWeapon(shield, weapon);

    if (weapon) {
        totalDefense = weapon->getDefense() + weapon->getExtraDefense();
        defenseSkill = getWeaponSkill(weapon);
    }

    if (shield) {
        totalDefense = (weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense());
        defenseSkill = getSkillLevel(SKILL_SHIELD);
    }

    if (defenseSkill == 0) {
        switch (fightMode) {
            case FIGHTMODE_ATTACK:
            case FIGHTMODE_BALANCED:
                return 1;

            case FIGHTMODE_DEFENSE:
                return 2;
        }
    }

    // Adjust defense based on fight mode
    if (fightMode == FIGHTMODE_ATTACK) {
        totalDefense -= totalDefense * 4 / 10;
    } else if (fightMode == FIGHTMODE_DEFENSE) {
        totalDefense += totalDefense * 8 / 10;
    }

    if (totalDefense > 0) {
        int32_t formula = (5 * defenseSkill + 50) * totalDefense;
        int32_t randresult = rand() % 100;
        totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000;
    }

    std::cout << "Total Defense: " << totalDefense << std::endl;
    return totalDefense;
}
 
Last edited:
I'm never gonna host the real files, so if anyone is interested and I still wanna do this when I get sober, I can release my modified binary with real-time exp-stage. It's not tested more than with 2 ppl on, so idk if stable edit or not. And I do not remember any details about it
 
Last edited:
Thank you both for that!

I was able to adjust the getArmor function correctly. However, I have some issues with the defense formula. Currently, it looks as such:

I'm dying to fast, I compared with @kay server Tibiantis, and let myself get hit by a rat with a jacket, on my server I die much faster than on kays.

C++:
int32_t Player::getDefense() const
{
    int32_t defenseSkill = getSkillLevel(SKILL_FIST);
    int32_t totalDefense = 5; // Base defense value
    const Item* weapon;
    const Item* shield;
    getShieldAndWeapon(shield, weapon);

    if (weapon) {
        totalDefense = weapon->getDefense() + weapon->getExtraDefense();
        defenseSkill = getWeaponSkill(weapon);
    }

    if (shield) {
        totalDefense = (weapon != nullptr ? shield->getDefense() + weapon->getExtraDefense() : shield->getDefense());
        defenseSkill = getSkillLevel(SKILL_SHIELD);
    }

    if (defenseSkill == 0) {
        switch (fightMode) {
            case FIGHTMODE_ATTACK:
            case FIGHTMODE_BALANCED:
                return 1;

            case FIGHTMODE_DEFENSE:
                return 2;
        }
    }

    // Adjust defense based on fight mode
    if (fightMode == FIGHTMODE_ATTACK) {
        totalDefense -= totalDefense * 4 / 10;
    } else if (fightMode == FIGHTMODE_DEFENSE) {
        totalDefense += totalDefense * 8 / 10;
    }

    if (totalDefense > 0) {
        int32_t formula = (5 * defenseSkill + 50) * totalDefense;
        int32_t randresult = rand() % 100;
        totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000;
    }

    std::cout << "Total Defense: " << totalDefense << std::endl;
    return totalDefense;
}
Besides your getExtraDefense() extra logic, I see your formula right.
Did you check your attack factors are right? You might also look at your monster getDefense() and doAttacking() formulas. Also
Weapons::getMaxMeleeDamage asuming you're using TFS based distro.
You may also want to check if monster file attack, defense and armor values are according to the mon files.
 
Last edited:
Besides your getExtraDefense() extra logic, I see your formula right.
Did you check your attack factors are right? You might also look at your monster getDefense() and doAttacking() formulas. Also
Weapons::getMaxMeleeDamage asuming you're using TFS based distro.
You may also want to check if monster file attack, defense and armor values are according to the mon files.

You're right, that getExtraDefense() function was to call the extra defense from weapons, there were weapons in higher versions of tibia that had those. I've removed them and will only use the currently used weapon and shield held by the character.

You were also right about the function getMaxMeleeDamage(). I had to adjust that function which was quite easy for monsters. But I'm unsure of players attack formula. As @kay mentioned in the previous post the defense and attack formula is the same, but what happens with the attack factors? Full defense, full attack, balance, and full defense during an attack?

Where is the factor determined in this formula @Terotrificy

(5 * skill + 50) * value * (rand()%100 + rand()%100)/2 / 10000.
 
I
You're right, that getExtraDefense() function was to call the extra defense from weapons, there were weapons in higher versions of tibia that had those. I've removed them and will only use the currently used weapon and shield held by the character.

You were also right about the function getMaxMeleeDamage(). I had to adjust that function which was quite easy for monsters. But I'm unsure of players attack formula. As @kay mentioned in the previous post the defense and attack formula is the same, but what happens with the attack factors? Full defense, full attack, balance, and full defense during an attack?

Where is the factor determined in this formula @Terotrificy

(5 * skill + 50) * value * (rand()%100 + rand()%100)/2 / 10000.
I don't think you need the attack factor in this formula, just assume monster is in balanced state so it equals 1, hence value * 1 is not different than the output you already have:
(5 * skill + 50) * (1.0f * value) * (rand()%100 + rand()%100)/2 / 10000.
 
But I'm unsure of players attack formula. As @kay mentioned in the previous post the defense and attack formula is the same, but what happens with the attack factors? Full defense, full attack, balance, and full defense during an attack?

Where is the factor determined in this formula @Terotrificy

(5 * skill + 50) * value * (rand()%100 + rand()%100)/2 / 10000.
"Value" stands for atk or def value multiplied by the fight stance factor. These are following:
  • full atk: 1.2 (rounded down) / 0.6 (rounded up)
  • balance: 1 / 1
  • full def: 0.6 (rounded up) / 1.8 (rounded down)
E.g. giant sword stats are: 46/22 on balance, 55/14 on full atk, 28/39 on full def.

Monsters use balance in fight. Any creature (player or monster) that is not engaged in fight (i.e. has no target) is always considered full def, regardless of its actual stance. (There is a minor bug to this due to which a summon can still be considered in balance after losing the target.)
 
Back
Top