|
|
AlexSpl
Responsible
Supreme Hero
|
posted October 31, 2024 07:42 PM |
|
|
It's a complex task. Basically, you have to check if AI consider to cast a spell at all. Then, if yes, you modify this method -
int (__thiscall *__stdcall type_AI_spellcaster::get_enchantment_function(
int spell))(type_AI_spellcaster *this, const army *our_army, type_enchant_data caster)
{
int (__thiscall *result)(type_AI_spellcaster *, const army *, type_enchant_data); // eax
switch ( spell )
{
case SPELL_MAGIC_ARROW:
case SPELL_ICE_BOLT:
case SPELL_LIGHTNING_BOLT:
case SPELL_IMPLOSION:
case SPELL_TITANS_LIGHTNING_BOLT:
result = type_AI_spellcaster::get_damage_spell_value;
break;
case SPELL_SHIELD:
result = type_AI_spellcaster::get_shield_value;
break;
case SPELL_AIR_SHIELD:
result = type_AI_spellcaster::get_air_shield_value;
break;
case SPELL_FIRE_SHIELD:
result = type_AI_spellcaster::get_fire_shield_value;
break;
case SPELL_PROTECTION_FROM_AIR:
result = type_AI_spellcaster::get_air_protection_value;
break;
case SPELL_PROTECTION_FROM_FIRE:
result = type_AI_spellcaster::get_fire_protection_value;
break;
case SPELL_PROTECTION_FROM_WATER:
result = type_AI_spellcaster::get_water_protection_value;
break;
case SPELL_PROTECTION_FROM_EARTH:
result = type_AI_spellcaster::get_earth_protection_value;
break;
case SPELL_ANTI_MAGIC:
result = type_AI_spellcaster::get_antimagic_value;
break;
case SPELL_DISPEL:
result = type_AI_spellcaster::get_dispel_value;
break;
case SPELL_MAGIC_MIRROR:
result = type_AI_spellcaster::get_backlash_value;
break;
case SPELL_CURE:
result = type_AI_spellcaster::get_cure_value;
break;
case SPELL_BLESS:
result = type_AI_spellcaster::get_bless_value;
break;
case SPELL_CURSE:
result = type_AI_spellcaster::get_curse_value;
break;
case SPELL_BLOODLUST:
result = type_AI_spellcaster::get_blood_lust_value;
break;
case SPELL_PRECISION:
result = type_AI_spellcaster::get_precision_value;
break;
case SPELL_WEAKNESS:
result = type_AI_spellcaster::get_weakness_value;
break;
case SPELL_STONE_SKIN:
result = type_AI_spellcaster::get_stone_skin_value;
break;
case SPELL_DISRUPTING_RAY:
result = type_AI_spellcaster::get_disruptive_ray_value;
break;
case SPELL_PRAYER:
result = type_AI_spellcaster::get_prayer_value;
break;
case SPELL_MIRTH:
result = type_AI_spellcaster::get_mirth_value;
break;
case SPELL_SORROW:
result = type_AI_spellcaster::get_sorrow_value;
break;
case SPELL_FORTUNE:
result = type_AI_spellcaster::get_fortune_value;
break;
case SPELL_MISFORTUNE:
result = type_AI_spellcaster::get_misfortune_value;
break;
case SPELL_HASTE:
result = type_AI_spellcaster::get_haste_value;
break;
case SPELL_SLOW:
result = type_AI_spellcaster::get_slow_value;
break;
case SPELL_SLAYER:
result = type_AI_spellcaster::get_slayer_value;
break;
case SPELL_FRENZY:
result = type_AI_spellcaster::get_frenzy_value;
break;
case SPELL_COUNTERSTRIKE:
result = type_AI_spellcaster::get_counterstrike_value;
break;
case SPELL_BERSERK:
result = type_AI_spellcaster::get_berserk_value;
break;
case SPELL_HYPNOTIZE:
result = type_AI_spellcaster::get_hypnotize_value;
break;
case SPELL_FORGETFULNESS:
result = type_AI_spellcaster::get_forgetfulness_value;
break;
case SPELL_BLIND:
case SPELL_PARALYZE:
result = type_AI_spellcaster::get_blind_or_paralyze_value;
break;
case SPELL_CLONE:
result = type_AI_spellcaster::get_clone_value;
break;
case SPELL_POISON:
result = type_AI_spellcaster::get_poison_value;
break;
case SPELL_DESEASE:
result = type_AI_spellcaster::get_disease_value;
break;
case SPELL_AGE:
result = type_AI_spellcaster::get_age_value;
break;
default:
result = type_AI_spellcaster::unimplemented;
break;
}
return result;
}
You see the default case, which is for any spell that hasn't its own weighting function. First what you should do is to write something like this -
case SPELL_AGE:
case SPELL_QUICKSAND:
result = type_AI_spellcaster::get_age_value;
break;
default:
result = type_AI_spellcaster::unimplemented;
break;
to force AI considering Quicksand with the Age spell weighting function for evaluation. Age spell has very simple weighting function, so you don't break the game. To do it in hex you have to change an index in the corresponding indirect table for the above switch statement. Might be a small patch, or might not
|
|
phoenix4ever
Legendary Hero
Heroes is love, Heroes is life
|
posted October 31, 2024 08:10 PM |
|
|
Damn seems pretty complicated to me and I hoped it could be done through simple hex editing.
Oh well, maybe it can help someone else.
Nice work.
|
|
Karyoplasma
Hired Hero
|
posted October 31, 2024 08:17 PM |
|
|
at one point i'll have to learn how to make c++ plugins.
is there a hooking template or even a modloader like monomod for unity games? that would make it easier to start.
just a general inquiry, i don't have any immediate plans as i'm a bit out of order currently as i broke my hand being an idiot lol
|
|
AlexSpl
Responsible
Supreme Hero
|
posted October 31, 2024 08:22 PM |
|
Edited by AlexSpl at 20:33, 31 Oct 2024.
|
You can start here. It's in Russian, but Google Translate is quite good for the task. Once you built your first plugin (see the example), it's a matter of time when you'll start to write your own. Many people started from zero. Now they write their non-trivial mods.
There you can find many examples, like this one. It's called FairWait. Speed of your troops is halved if you wait. There are also many not-so-experimental examples.
Many plugins to download can be found here. These were tested and used in real (online) games. I'm sure you find there one you'll like. Note that they are for Complete/SoD with the HD mod. It's how things officially are.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted October 31, 2024 09:45 PM |
|
|
About the future of modding. First of all, you have to decide which core of the game you like to modify. Why not VCMI if it's open source? Well, it's not the original game, though it's very close to it. 99% of players will be OK with VCMI. I recommend to modify VCMI, if you started to play Heroes recently. If you want to make mods for the original game and HotA, I suggest you to learn NH3API. Actually, this API isn't yet published, but it will standardize all the modding around the original game. It's the main purpose of NH3API. Modders will get access to the original methods of the game with the original names. There was only one such an attempt I know of (see H3API by RoseKavalier). NH3API will provide that much and more (I hope). All the plugins written before will be easily transferred to the NH3API. It's the greatest attempt to standardize H3 modding, I doubt someone ever start such an ambitious task in the near future, so I reckon it will have all chances to become a new robust platform for all the H3 modders around the world.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 01, 2024 12:02 PM |
|
|
Looked closer at the type_AI_spellcaster class methods. The above method is used only for evaluation of spells with ID 15 and more (starting from Magic Arrow). To evaluate Quicksand and other spells with ID less than 15 you have to add a case to void __thiscall type_AI_spellcaster::consider_spell(type_AI_spellcaster *this, type_spell_choice *choice) method.
switch ( iSpell )
{
case SPELL_EARTHQUAKE:
type_AI_spellcaster::consider_earthquake(this, choice);
break;
case SPELL_CHAIN_LIGHTNING:
type_AI_spellcaster::consider_chain_lightning(this, choice);
break;
...
In other words, you have to write
case SPELL_QUICKSAND:
type_AI_spellcaster::consider_quicksand(this, choice);
break;
for Quicksand, for example. The main difficulty here is to write consider_quicksand(this, choice); But if you just want AI to cast it, you simply may write (let Quicksand has constant weight of 1,000) -
choice->value = 1000;
choice->cast_now = 1;
And, voila, AI casts Quicksand simply because it knows this spell.
|
|
Karyoplasma
Hired Hero
|
posted November 01, 2024 10:38 PM |
|
|
Thanks for all the info, AlexSPL. I'll be checking it out.
|
|
SilverG
Known Hero
|
posted November 03, 2024 02:49 PM |
|
|
Hello,
in the HD.exe where can I find the cost for hiring a hero... 2500gold seems too cheap.
Thank you.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 03, 2024 03:02 PM |
|
|
See int gHeroGoldCost @ 27814Ch.
|
|
SilverG
Known Hero
|
posted November 03, 2024 03:10 PM |
|
|
Thanks.
|
|
VIP
Known Hero
|
posted November 03, 2024 03:48 PM |
|
|
I wrote with help: functions HotA for H3 Complete / Chronicles .
|
|
Csaros
Hired Hero
|
posted November 05, 2024 01:44 PM |
|
Edited by Csaros at 13:57, 05 Nov 2024.
|
Thanks a lot for help Alex!
I am now trying to make an artifact provide the magi -2 mana cost bonus. Do you know how to make an artifact check work?
My invalid code looks like this:
0xe558b: sandwiched in a jump to free space right after the Magi check, before comparison of 83 FE 01 at 0xe558c
0xf83f7 (free space):
68 8B 00 00 00 push x8b (artifact ID)
8B 4D C4 - mov ecx, [ebp-0x3c] (same as the function used to look for hero data in the Magic Channel ability check)
E8 5B 10 FE FF call artifact check at 4D9460
84 C0 test al, al
74 03 je 3
83 EE 02 sub esi, 2 (reduce mana cost)
E9 7B D1 FE FF (jump back to original function, to the point marked in bold)
Do you know how can I fix it? I suppose the value of ecx should be different - what should I set it to for the function to find the artifact?
Thank you from the top!
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 05, 2024 03:20 PM |
|
|
This method looks like this -
int __thiscall hero::GetManaCost(hero *this, SpellID iWhichSpell, const armyGroup *enemy, char magic_terrain)
{
int v6; // eax
type_artifact *equipped; // ecx
EComboArtifact m_targetCombo; // eax
TSkillMastery SpellSchoolLevel; // eax
int v10; // esi
if ( iWhichSpell == SPELL_TITANS_LIGHTNING_BOLT )
return 0;
if ( iWhichSpell == SPELL_ARMAGEDDON )
{
v6 = 0;
equipped = this->equipped;
do
{
if ( equipped->type == ARTIFACT_ARMAGEDDONS_BLADE )
goto LABEL_9;
++v6;
++equipped;
}
while ( v6 < 19 );
m_targetCombo = akArtifactTraits[128].m_targetCombo;
if ( m_targetCombo != COMBO_NONE && hero::IsWieldingArtifact(this, combo_artifacts[m_targetCombo].type) )
{
LABEL_9:
SpellSchoolLevel = eMasteryExpert;
goto LABEL_11;
}
}
SpellSchoolLevel = hero::GetSpellSchoolLevel(this, akSpellTraits[iWhichSpell].m_school, magic_terrain);
LABEL_11:
v10 = akSpellTraits[iWhichSpell].m_manaCost[SpellSchoolLevel];
if ( enemy )
{
if ( armyGroup::IsMember(enemy, CREATURE_PEGASUS) || armyGroup::IsMember(enemy, CREATURE_SILVER_PEGASUS) )
v10 += 2;
if ( armyGroup::IsMember(&this->heroArmy, CREATURE_MAGE) || armyGroup::IsMember(&this->heroArmy, CREATURE_ARCH_MAGE) )
v10 -= 2;
}
if ( v10 < 1 )
return 1;
return v10;
}
Make sure your jmp (5 bytes) didn't overwrite the next instruction. Make sure you didn't break the stack (push/pop). If it's not the reason I'll look into it later.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 05, 2024 03:35 PM |
|
Edited by AlexSpl at 15:48, 05 Nov 2024.
|
Basically, you have to write this -
if ( hero::IsWieldingArtifact(this, YOUR_ARTIFACT_ID) )
v10 -= 2
v10 being the resulting mana cost. Probably, you just broke the stack (common mistake).
This is also might be incorrect. Check where your artifact ID is really stored.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 05, 2024 03:56 PM |
|
|
Do you keep this pop edi in your free space, btw?
|
|
Csaros
Hired Hero
|
posted November 05, 2024 04:24 PM |
|
Edited by Csaros at 16:24, 05 Nov 2024.
|
I didn't touch the rest of the code; all the pops and comparisons etc. are in the right places. I used a short jump overwriting 83ee02 immediately before 83fe01 to an empty space nearby, used that to place my 83ee02 as well as the jump to the free space. The effect works for magi and archmagi, only the artifact ID is not checked correctly. I think my ecx assignment may be incorrect.
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 05, 2024 04:33 PM |
|
|
What you are using ecx for? You already pushed your artifact ID to the stack?
|
|
AlexSpl
Responsible
Supreme Hero
|
posted November 05, 2024 04:39 PM |
|
Edited by AlexSpl at 16:55, 05 Nov 2024.
|
Oh, okay, you are trying to use this pointer, try edi instead. It is __thiscall, you push artifact ID to the stack, and this to ecx. I believe, [ebp-0x3c] is not your this (pointer to hero; hero*).
Well, my bad, edi is also rewritten. Try ebx. Should work.
|
|
Phoenix4ever
Legendary Hero
Heroes is love, Heroes is life
|
posted November 05, 2024 05:50 PM |
|
|
Hi Alex
I might as well ask you directly, since you seem to know (almost) everything about modding H3.
So you know you can buy skills in universities, for 2000 gold, right.
Is there any way to add a function to universities, that allows you to pay 2000 gold to delete a skill?
Maybe Witch Huts could also allow you to delete a certain skill? Or maybe even adventure map scholars?
Or is there any other way to achieve this?
Skills like Pathfinding, Navigation and generally bad skills like Eagle Eye, Learning and Ballistics will usually become useless sooner or later, so it would be nice with some way to delete skills. (Also if it costs some gold, as with the university example.)
|
|
Csaros
Hired Hero
|
posted November 05, 2024 09:22 PM |
|
|
The mov ecx,ebx worked! Thanks a lot, Alex!
|
|
|