|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 02:33 PM |
|
|
|
I think it's better to cap amount of joining monsters. Say, with Basic Diplomacy you can join 25% of monsters, but no more than their 1 basic growth. With Advanced Diplomacy, 50%/2 growths. With Expert Diplomacy, 75%/3 growths. Though 3 Archangels from one neutral group still look too many.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 03:22 PM |
|
|
AlexSpl said: I think it's better to cap amount of joining monsters. Say, with Basic Diplomacy you can join 25% of monsters, but no more than their 1 basic growth. With Advanced Diplomacy, 50%/2 growths. With Expert Diplomacy, 75%/3 growths. Though 3 Archangels from one neutral group still look too many.
So thats an idea, and I think there was a similar mechanics in homm2? The thing is, this feels like a buff to diplomacy, while I am trying to nerf it.
Another question, say i want the formula to be v16 + 0, lea ecx,[edx+ecx] becomes 8d 0c 0a, what do I put in the 4th byte? 00? 90?
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 04:23 PM |
|
|
Yes, NOP (90h) is what you need as a padding. You can also use it to 'erase' whole instructions.
Just for fun 
It's what Intel recommends to use when we need more than 1 NOP -
Table 4-12. Recommended Multi-Byte Sequence of NOP Instruction
Length Assembly Byte Sequence
2 bytes 66 NOP 66 90H
3 bytes NOP DWORD ptr [EAX] 0F 1F 00H
4 bytes NOP DWORD ptr [EAX + 00H] 0F 1F 40 00H
5 bytes NOP DWORD ptr [EAX + EAX*1 + 00H] 0F 1F 44 00 00H
6 bytes 66 NOP DWORD ptr [EAX + EAX*1 + 00H] 66 0F 1F 44 00 00H
7 bytes NOP DWORD ptr [EAX + 00000000H] 0F 1F 80 00 00 00 00H
8 bytes NOP DWORD ptr [EAX + EAX*1 + 00000000H] 0F 1F 84 00 00 00 00 00H
9 bytes 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H] 66 0F 1F 84 00 00 00 00 00H
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 07:40 PM |
|
|
|
Speaking of erasing whole instructions. Do you know the location of the code responsible for Wisdom (and School of magic skills?) being offered every 3/6 levels to might/magic heroes? Can it be isolated or is it a series of bits of code scattered around the larger function for level up skill offers? I would like to get rid of this rule completely so that Wisdom and School of magic skills are only offered "randomly" according to the probabilities specified for each hero class.
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 08:04 PM |
|
Edited by AlexSpl at 20:09, 05 Mar 2026.
|
Yes, it's there -
TSecondarySkill __fastcall get_skill_award(
hero *current_hero,
TSkillMastery min_level,
int max_level,
TSecondarySkill excluded)
{
hero *v4; // edi
THeroClass hero_class; // eax
int v6; // eax
int v7; // esi
int Level; // ecx
__int32 v9; // eax
TSecondarySkill v10; // eax
TSecondarySkill result; // eax
int v12; // edx
const TSecondarySkill *v13; // esi
TSecondarySkill v14; // eax
char v15; // bl
int v16; // ebx
TSecondarySkill *v17; // esi
char v18; // dl
int v19; // esi
int v20; // eax
char v21; // bl
int v22; // ecx
int v23; // esi
char v24; // dl
int v25; // ecx
THeroClassTraits *v26; // [esp+Ch] [ebp-10h]
_BYTE *ss_disabled; // [esp+14h] [ebp-8h]
TSkillMastery v29; // [esp+18h] [ebp-4h]
v4 = current_hero;
v29 = min_level;
hero_class = current_hero->hero_class;
v26 = (THeroClassTraits *)(akHeroClassTraits[0].m_townType + (hero_class << 6));
ss_disabled = gpGame->ss_disabled;
if ( gbInCampaign && gpGame->sCampaign.iCurrentCampaign == SOD_BIRTH_OF_A_BARBARIAN && current_hero->id == HERO_SOLMYR )
ss_disabled = Campaign14SolmyrBannedSkills;
if ( current_hero->numSSs >= 8 )
{
v29 = eMasteryBasic;
min_level = eMasteryBasic;
}
if ( min_level >= max_level )
return -1;
if ( hero_class == eClassCleric
|| hero_class == eClassDruid
|| hero_class == eClassWizard
|| hero_class == eClassHeretic
|| hero_class == eClassNecromancer
|| hero_class == eClassWarlock
|| hero_class == eClassBattleMage
|| hero_class == eClassWitch )
{
v6 = 3;
v7 = 3;
}
else
{
v6 = 6;
v7 = 4;
}
Level = current_hero->Level;
if ( v6 + v4->lastWisdom > Level || (v9 = v4->SSLevel[SKILL_WISDOM], v9 >= max_level) || v9 < min_level )
{
v10 = excluded;
}
else
{
v10 = excluded;
if ( excluded != SKILL_WISDOM && !ss_disabled[7] )
return 7;
}
if ( v7 + v4->last_magic_school_level > Level
|| v10 == SKILL_FIRE_MAGIC
|| v10 == SKILL_AIR_MAGIC
|| v10 == SKILL_WATER_MAGIC
|| v10 == SKILL_EARTH_MAGIC )
{
goto LABEL_50;
}
v12 = 0;
v13 = schools;
while ( 1 )
{
v14 = *v13;
v15 = v4->SSLevel[*v13];
if ( v15 < max_level && v15 >= v29 && !ss_disabled[v14] )
{
if ( v15 <= 0 )
v12 += v26->m_gainSecondarySkillChance[v14];
else
++v12;
}
if ( (int)++v13 >= (int)"HeroSpec.txt" )
break;
v4 = current_hero;
}
if ( v12 <= 0 )
{
LABEL_49:
v4 = current_hero;
LABEL_50:
v19 = 0;
v20 = 0;
while ( 1 )
{
v21 = v4->SSLevel[v20];
if ( v21 >= max_level || v21 < v29 || v20 == excluded )
goto LABEL_59;
if ( ss_disabled[v20] )
break;
v22 = v26->m_gainSecondarySkillChance[v20];
if ( !v26->m_gainSecondarySkillChance[v20] )
goto LABEL_56;
LABEL_58:
v19 += v22;
LABEL_59:
if ( ++v20 >= 28 )
{
if ( v19 )
{
v23 = Random(1, v19);
result = SKILL_PATHFINDING;
while ( 1 )
{
v24 = v4->SSLevel[result];
if ( v24 < max_level && v24 >= v29 && result != excluded )
break;
LABEL_73:
if ( ++result >= MAX_SECONDARY_SKILLS )
return -1;
}
if ( !ss_disabled[result] )
{
v25 = v26->m_gainSecondarySkillChance[result];
if ( !v26->m_gainSecondarySkillChance[result] )
{
LABEL_70:
if ( v24 > 0 )
v25 = 1;
}
v23 -= v25;
if ( v23 <= 0 )
return result;
goto LABEL_73;
}
v25 = 0;
goto LABEL_70;
}
return -1;
}
}
v22 = 0;
LABEL_56:
if ( v21 > 0 )
v22 = 1;
goto LABEL_58;
}
v16 = Random(1, v12);
v17 = (TSecondarySkill *)schools;
while ( 1 )
{
result = *v17;
v18 = current_hero->SSLevel[*v17];
if ( v18 < max_level && v18 >= v29 && !ss_disabled[result] )
{
if ( v18 <= 0 )
v16 -= v26->m_gainSecondarySkillChance[result];
else
--v16;
if ( v16 <= 0 )
return result;
}
if ( (int)++v17 >= (int)"HeroSpec.txt" )
goto LABEL_49;
}
}
Checks for classes start at 4DAFE4h. Btw, you can notice there are no checks for Conflux heroes (bug), so its both classes are treated as Might.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 08:24 PM |
|
|
{
v6 = 3;
v7 = 3;
}
else
{
v6 = 6;
v7 = 4;
}
I could probably neutralise it by giving these variables some ridiculously high values? Whats the highest I can put here? 255?
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 08:29 PM |
|
|
Probably, it's better to get rid of this logic completely. Just NOP the following code (or write a jmp past it till here - 4DB096h) -
if ( v6 + v4->lastWisdom > Level || (v9 = v4->SSLevel[SKILL_WISDOM], v9 >= max_level) || v9 < min_level )
{
v10 = excluded;
}
else
{
v10 = excluded;
if ( excluded != SKILL_WISDOM && !ss_disabled[7] )
return 7;
}
if ( v7 + v4->last_magic_school_level > Level
|| v10 == SKILL_FIRE_MAGIC
|| v10 == SKILL_AIR_MAGIC
|| v10 == SKILL_WATER_MAGIC
|| v10 == SKILL_EARTH_MAGIC )
{
goto LABEL_50;
}
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 08:36 PM |
|
|
|
Is it better? Genuine question.
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 08:45 PM |
|
Edited by AlexSpl at 21:00, 05 Mar 2026.
|
Noping will give you a free space, if you want it. Jumping is an elegant solution. Usually it's two bytes EB xx (where xx if how far or back you jump), or five bytes (E9 xx xx xx xx), if the length of the code you want to jump over is more than 128 bytes forth or -127 bytes back. Usually, you have to check, if any registers are defined in the code you want to erase, but I don't see any.
So, try to jmp from 4DAFDBh to 4DB096h. Edited a bit.
I see that distance is bigger than 128 bytes forward just by subtracting: 4DB096h - 4DAFDBh + 2 (2 is EB xx itself) > 128, so use E9 xx xx xx xx: 4DB096 - 4DAFDB + 5 (5 is E9 xx xx xx xx). Test and use jmps freely
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 09:07 PM |
|
Edited by BigPig2 at 21:07, 05 Mar 2026.
|
I understand. I dont think Im yet at the point of needing space for anything and I feel like just switching up the values allows me to reverse the change easily should it turn out to cause some issues down the line.
Btw, I believe I see
{
v6 = 3;
v7 = v6;
}
else
{
v6 = 6;
v7 = 4;
}
Could that be correct? Also, in the hex code, v6 and v7 get mentioned first but I guess thats just a property of its syntax?
So:
06 at DB00D is the 6
04 at DB012 is the 4, and
03 at DB019 is the 3
So i can just set all these three to 25 and effectively mute this whole procedure?
And the 8B F0 at DB01D-E is the v7 = v6 ? How come when I reverse-assemble "mov esi,eax" I get 89 c6 instead though?
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 09:15 PM |
|
|
Correct, you see
mov eax, 6
mov esi, 4
for Might heroes and
mov eax, 3
mov esi, eax
for Magic heroes (with compiler optimization)
You can make 6, 4, and 3 very big numbers, and it will work, but we call it 'a crutch', as it doesn't look beautiful or effective. The game still will be running those instructions which you could just skip, never to trigger the intended logic. So, try to make those numbers big, and when it will work, try to just jmp over them.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 09:27 PM |
|
Edited by BigPig2 at 21:28, 05 Mar 2026.
|
So u mean its good habits basically? I will keep that in mind.
I set the three values to 25 because by then the hero should have a full set of skills.
I am wondering about another thing:
if ( v7 + v4->last_magic_school_level > Level
|| v10 == SKILL_FIRE_MAGIC
|| v10 == SKILL_AIR_MAGIC
|| v10 == SKILL_WATER_MAGIC
|| v10 == SKILL_EARTH_MAGIC )
Does this part mean one of the schools is chosen at random or will it check for probabilities according to hero class at some later point? I am still struggling to read some parts of this code, not familiar with the syntax.
Also, what is that bit about pathfinding?
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 09:38 PM |
|
Edited by AlexSpl at 21:41, 05 Mar 2026.
|
Quote: I am wondering about another thing
This condition rockets you to LABEL_50, thus skipping the part of the code where skills are given according to their weights. That's why it called an exception. Magic schools just skip voting process 
Quote: Also, what is that bit about pathfinding?
Don't pay attention yet, Pathfinding just has ID of zero, so every iterator starts from it, from the first available skill.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 10:07 PM |
|
|
Yet another reason to ditch this procedure then.
Ok but that is it then, my problems 2. and 5. are solved, I will play with the constants, I have a feeling v16+0 / v16+2 might be what I end up going for. Thank you so much for your help!
Btw, speaking of skill weights. I have seen multiple mentions of "make sure the probabilities add up to 112" (I think is the number?) and similarly "spell probabilities have to add up to 100", what happens if the dont exactly? I have already altered both in many spots but I have not played enough games with the changes applied to notice any ill or side effects.
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 10:11 PM |
|
|
Quote: Btw, speaking of skill weights. I have seen multiple mentions of "make sure the probabilities add up to 112" (I think is the number?) and similarly "spell probabilities have to add up to 100", what happens if the dont exactly? I have already altered both in many spots but I have not played enough games with the changes applied to notice any ill or side effects.
Forget 112. They sum up. Feel free to set up any you want.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 10:20 PM |
|
|
|
And the spells (mage guild occurence probabilities)?
|
|
AlexSpl

   
    
Responsible
Supreme Hero
|
posted March 05, 2026 10:33 PM |
|
|
|
Just experiment. They will sum up. The single thing that is important is the weight of a spell divided by the sum.
|
|
BTB 

 
   
Famous Hero
Moist & Creamy
|
posted March 05, 2026 10:46 PM |
|
|
BigPig2 said: Btw, speaking of skill weights. I have seen multiple mentions of "make sure the probabilities add up to 112" (I think is the number?) and similarly "spell probabilities have to add up to 100", what happens if the dont exactly? I have already altered both in many spots but I have not played enough games with the changes applied to notice any ill or side effects.
This is something that can be redacted. It's a statement I made before I dug deep enough into the code to disprove it.
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 10:54 PM |
|
|
Alright, that was my suspicion too but I just saw it in multiple sources and its just kind of hard to test it empirically.
Last question for the day then. How can I find the values of maximum number of heroes allowed to AI players per difficulty level? The 3/12, 4/15, 5/18 and 6/21. I feel like these values were probably justified with the memory constraints back when the game came out, and I understand things might get weird with like 60 heroes on the map but I would really like to play with these numbers and see if I find a better spot..
|
|
BigPig2

 
Tavern Dweller
|
posted March 05, 2026 10:57 PM |
|
|
BTB said: It's a statement I made
You and a whole lot of posters in other threads, and I believe on other sites as well, it feels like its "common knowledge".
|
|
|
|