STEAM GROUP
Final Fantasy - Modding FF-Modding
STEAM GROUP
Final Fantasy - Modding FF-Modding
147
IN-GAME
1,465
ONLINE
Founded
July 26, 2015
Language
English
Location
United States 
All Discussions > Final Fantasy IX > Topic Details
Tirlititi Jun 9, 2016 @ 10:41am
Hades Workshop
Hades Workshop is the most complete Game Editing Tool available for Final Fantasy IX. Its development started for the PSX version and it became compatible with the PC version as well.

It's available on Qhimm:
http://forums.qhimm.com/index.php?topic=14315.0

If you want to contribute, you can read this post:
http://forums.qhimm.com/index.php?topic=14315.msg248197#msg248197

This tool is not compatible with Albeoris's Memoria for the moment.
Last edited by Tirlititi; Feb 18, 2017 @ 10:35am
< >
Showing 1-15 of 88 comments
Albeoris Jun 10, 2016 @ 5:53pm 
Good job! Do you change Assembly-CSharp.dll? If you don't - yes, you can apply Memoria after HW. But items and abilities data will be loaded from external CSV-tables. Otherwise, you get a checksum error.
Any ideas about integration? (:
https://github.com/Albeoris/Memoria
Tirlititi Jun 11, 2016 @ 12:57am 
Yes I change that .dll.

Do you mean integration of Memoria into Hades Workshop? Or the other way around?
Both cases will need work as you're in C# and I'm in C++. There seems to be solutions to use both in one project, but those are quiet heavy. The COM feature seems to do that, though I didn't take the time to look at it.

What I can do is replacing parts of IL code's methods by custom parts. It shouldn't be too difficult to copy your custom IL code for, let's say, the fast battle swirl, and add a feature in HW to integrate it.

What's the purpose of having an external Memoria.dll, by the way? You're modifying Assembly-CSharp.dll already, so is there a reason why you don't do all the modifications inside it?
Albeoris Jun 11, 2016 @ 4:04am 
No matter . :) We can use C++/CLI instead COM.
Yeah, I started with IL-code editing. For example:
https://github.com/Albeoris/Memoria/blob/master/Memoria.Patcher/Patches/FF9TextToolPatch.cs

But now I move parts of the game logic in my own library. Look at ff9item class:
https://github.com/Albeoris/Memoria/blob/master/Memoria/Engine/Data/ff9item.cs

I remove this type from the original assembly and add a redirection to Memoria.dll.
In the future, I want to do this for all scenes, the game data and events.
To give modmakers a convenient mechanism for expanding the game.
You can see all changed types here:
https://github.com/Albeoris/Memoria/tree/master/Memoria/Engine

Now I'm getting ready to move "EventEngine" and add my own scripting language to add to the game new characters and quests.

P.S. Your native language? D:
Last edited by Albeoris; Jun 11, 2016 @ 4:10am
Tirlititi Jun 11, 2016 @ 4:20am 
I saw how you modded things, but I don't see how it'd be more convenient to modify Memoria.dll instead of Assembly-CSharp.dll ^^'

My native language is russian... just kidding, I'm french :p
Albeoris Jun 11, 2016 @ 11:35am 
You have sources of Memoria.dll o,o
I just edit sources and comile a new version of Memoria.dll insteed patch IL-code. o,o
This is much more convenient. D:
Mark a whole type by the ExportedType attribute and move it into a separate assembly is easier than to patch every instruction what you want to modify. (=

Hehehe. I was surprised when I saw Kudos and RGR versions support. ^^''
Last edited by Albeoris; Jun 11, 2016 @ 11:39am
Moogy Jun 11, 2016 @ 7:47pm 
So this is mods to play with?
Im not a modder so i dont wanna apply anything that hasnt been confirmed but this looks very promising :)
Last edited by Moogy; Jun 11, 2016 @ 7:47pm
Tirlititi Jun 11, 2016 @ 11:54pm 
Originally posted by Albeoris:
I just edit sources and compile a new version of Memoria.dll insteed patch IL-code. o,o

Fair enough. Since I only patched the IL code, I forgot that you could compile dll usually and that the decompiled C# code wasn't so nice for that.

@Moogy : it's a tool to create mods. You can configure the game a bit like the spreadsheet edition in Memoria.
Tirlititi Jul 22, 2016 @ 1:19pm 
And here it is[www.hiveworkshop.com] :D

I'll create a thread for bug-fixing mods this week-end. Also, I'll import Alternate Fantasy to Steam !
Agosaxv2.7.2 Jul 25, 2016 @ 1:06am 
Hades Workshop is working great!

With this tool l added 1 more hit to Zedane's Grand Lethal so it hit twice for 9999 each lol.

Is it possible to increase an Enemy's HP to more than 65535 though? l want to give Ozma 222222 HP. Or are they capped due to memory bits/bytes thing?
Last edited by Agosaxv2.7.2; Jul 25, 2016 @ 1:14am
Tirlititi Jul 25, 2016 @ 4:04am 
Congrats ;)

It is not possible to directly increase the max HP more than 65535, but you can use a trick to bypass the problem. You have to edit the AI script for that. I copy-paste the detailed answer I made when the question was asked for Kuja. The same remarks about Curaga and his counter attacks hold for Ozma.

For AI script, the functions are usually the followings.
- A single main function that usually only inits the enemies with a InitObject call.
- For each enemies, a set of function that can be made of :
-- Init : usually defines which regular attacks the enemy will use and their mana cost (set the caret on the numbers and look at the fields "Attack List" and "4-tuple" on the left).
-- ATB : what the enemy does when its ATB is full.
-- Loop : a function that check the enemy's state each frame and may respond to it accordingly. It always ends with the lines "Wait(1)" and "loop". That's the one you're looking for. (optional but frequent)
-- Counter : what the enemy does when it has been hitted (optional)
-- CounterEx : what the enemy does when it casts a spell on himself or anytime the "counter" doesn't trigger for some reason (optional)
-- Death : what happens when the enemy dies (optional)

For most bosses, you may have noticed that they display 10 000 more HP than they should. Trance Kuja's HP is 55535 in-game, not 65535. That's because the looping function is scripted so when Kuja goes under 10 000 HP, he speaks, cast Ultima and ends the fight.
That's this part of the code specifically :
if ( #( SV_FunctionEnemy[HP] <$ 10000 ) ) { // Wait until Kuja no longer attacks while ( IsAttacking != 0 ) { Wait( 1 ) } // Dunno... Maybe a check of "The battle has started" if ( GetData_30 != 4 ) { return } // Freeze the ATB and hide it. RunBattleCode( 32, 0 ) while ( GetData_30 != 1 ) { Wait( 1 ) } // Cast Ultima (the speech is included in it) set #( SV_Target = SV_PlayerTeam ) AttackSpecial( 5 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } RunBattleCode( 40, 1 ) set VAR_B5_199 &= 65519 Wait( 1 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } // Fade filter and ends the fight FadeFilter( 0, 1, 0, 255, 255, 255 ) set VAR_B5_199 &= 65519 while ( IsAttacking != 0 ) { Wait( 1 ) } set SV_FunctionEnemy[DEFEATED_ON] =$ 1 RunBattleCode( 33, 5 ) return }

So, you see, to check if an enemy's HP is under 10 000, that's the line "#( SV_FunctionEnemy[HP] <$ 10000 )".
Using simply "SV_FunctionEnemy[HP] <$ 10000" should also work. The purpose of the # operator and $ operator modifier is to handle multiple characters at once.
For instance, the expression "#( SV_PlayerTeam[HP] ==$ 1 )" will return true if there is at least 1 character in the team whose HP is 1.
However, "SV_PlayerTeam[HP] == 1" won't work. It will return true only if the 1st character's HP is 1 and all the others' are 0.

More precisely, suppose you have 4 characters in the team and their HP are 100, 113, 210 and 95.
"SV_PlayerTeam[HP]" returns a list : [100, 113, 210, 95]
"SV_PlayerTeam[HP] >=$ 100" also returns a list : [1, 1, 1, 0]
"#( SV_PlayerTeam[HP] >=$ 100 )" returns the amount of bits on : 3

So, how do you increase the HP limit? By doing exactly what you suggested ! You take a variable ("VAR_B7_60" is fine for that purpose and completly unused), increment it and heal each time Kuja goes under 10 000 and launch the end of the battle only once it reaches a certain amount.
You also need to set the local variable counter to more than 61 in order to use "VAR_B7_60". That means you must replace the "allocate [NB]" by "allocate 61" in the local variable panel.

To heal, use this line :
set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP])
"FirstOf" converts a list [value1, value2, value3, value4] into value1.
You can also use :
set SV_FunctionEnemy[HP] =$ 65535

You may want to init VAR_B7_60 to 0 in the enemy's initialization function but it's always initialized to 0 by default.

The resultant code should look something like this :
Function func_Trance_Kuja_Loop if ( !VAR_B7_0 ) { set VAR_B7_0 = 1 while ( !( GetData_40 & 8 ) ) { Wait( 1 ) set VAR_B5_206 = GetRandom } set SV_FunctionEnemy[54] =$ 0 while ( GetData_30 != 1 ) { Wait( 1 ) set VAR_B5_206 = GetRandom } RunBattleCode( 35, 0 ) while ( GetData_30 != 4 ) { Wait( 1 ) } } if ( #( SV_FunctionEnemy[HP] <$ 10000 ) ) { if ( VAR_B7_60 < 5 ) { set VAR_B7_60++ set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP]) } else { while ( IsAttacking != 0 ) { Wait( 1 ) } if ( GetData_30 != 4 ) { return } RunBattleCode( 32, 0 ) while ( GetData_30 != 1 ) { Wait( 1 ) } set #( SV_Target = SV_PlayerTeam ) AttackSpecial( 5 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } RunBattleCode( 40, 1 ) set VAR_B5_199 &= 65519 Wait( 1 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } FadeFilter( 0, 1, 0, 255, 255, 255 ) set VAR_B5_199 &= 65519 while ( IsAttacking != 0 ) { Wait( 1 ) } set SV_FunctionEnemy[DEFEATED_ON] =$ 1 RunBattleCode( 33, 5 ) return } } Wait( 1 ) loop

Note that Kuja's counter-attacks are based on his current HP. You may want to change that also. And you may also want not to heal Kuja completly (let's say bring his HP to 55535 instead of 65535) so his Curaga still heals him.
Hope you'll be more at ease after that ^^

Note that if you import the file "LocalVariableSettings_v1.hws" (using Open Mod), the variables of AI scripts will be given names fitting their purpose, so it'll be a bit easier to understand the script.


However, there may be bugs in-game when Grand Lethal hits twice.
EDIT : Nevermind, the bugs are not triggering anymore in the Steam version. That's a good news ^^
Last edited by Tirlititi; Jul 25, 2016 @ 4:42am
Moogy Jul 25, 2016 @ 4:59am 
This sounds awesome, finally this port seems worth playing.

Are you gonna release some mods for people to download?
Last edited by Moogy; Jul 25, 2016 @ 4:59am
Tirlititi Jul 25, 2016 @ 5:09am 
Yes. I'm porting a mod I made for the PSX version, Alternate Fantasy.
I grab the occasion to improve it a bit. Should be available in the week.
Moogy Jul 25, 2016 @ 7:24am 
Originally posted by Tirlititi:
Yes. I'm porting a mod I made for the PSX version, Alternate Fantasy.
I grab the occasion to improve it a bit. Should be available in the week.

Fantastic !!
Cant wait !
Agosaxv2.7.2 Aug 8, 2016 @ 12:54pm 
Originally posted by Tirlititi:
Congrats ;)

It is not possible to directly increase the max HP more than 65535, but you can use a trick to bypass the problem. You have to edit the AI script for that. I copy-paste the detailed answer I made when the question was asked for Kuja. The same remarks about Curaga and his counter attacks hold for Ozma.

For AI script, the functions are usually the followings.
- A single main function that usually only inits the enemies with a InitObject call.
- For each enemies, a set of function that can be made of :
-- Init : usually defines which regular attacks the enemy will use and their mana cost (set the caret on the numbers and look at the fields "Attack List" and "4-tuple" on the left).
-- ATB : what the enemy does when its ATB is full.
-- Loop : a function that check the enemy's state each frame and may respond to it accordingly. It always ends with the lines "Wait(1)" and "loop". That's the one you're looking for. (optional but frequent)
-- Counter : what the enemy does when it has been hitted (optional)
-- CounterEx : what the enemy does when it casts a spell on himself or anytime the "counter" doesn't trigger for some reason (optional)
-- Death : what happens when the enemy dies (optional)

For most bosses, you may have noticed that they display 10 000 more HP than they should. Trance Kuja's HP is 55535 in-game, not 65535. That's because the looping function is scripted so when Kuja goes under 10 000 HP, he speaks, cast Ultima and ends the fight.
That's this part of the code specifically :
if ( #( SV_FunctionEnemy[HP] <$ 10000 ) ) { // Wait until Kuja no longer attacks while ( IsAttacking != 0 ) { Wait( 1 ) } // Dunno... Maybe a check of "The battle has started" if ( GetData_30 != 4 ) { return } // Freeze the ATB and hide it. RunBattleCode( 32, 0 ) while ( GetData_30 != 1 ) { Wait( 1 ) } // Cast Ultima (the speech is included in it) set #( SV_Target = SV_PlayerTeam ) AttackSpecial( 5 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } RunBattleCode( 40, 1 ) set VAR_B5_199 &= 65519 Wait( 1 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } // Fade filter and ends the fight FadeFilter( 0, 1, 0, 255, 255, 255 ) set VAR_B5_199 &= 65519 while ( IsAttacking != 0 ) { Wait( 1 ) } set SV_FunctionEnemy[DEFEATED_ON] =$ 1 RunBattleCode( 33, 5 ) return }

So, you see, to check if an enemy's HP is under 10 000, that's the line "#( SV_FunctionEnemy[HP] <$ 10000 )".
Using simply "SV_FunctionEnemy[HP] <$ 10000" should also work. The purpose of the # operator and $ operator modifier is to handle multiple characters at once.
For instance, the expression "#( SV_PlayerTeam[HP] ==$ 1 )" will return true if there is at least 1 character in the team whose HP is 1.
However, "SV_PlayerTeam[HP] == 1" won't work. It will return true only if the 1st character's HP is 1 and all the others' are 0.

More precisely, suppose you have 4 characters in the team and their HP are 100, 113, 210 and 95.
"SV_PlayerTeam[HP]" returns a list : [100, 113, 210, 95]
"SV_PlayerTeam[HP] >=$ 100" also returns a list : [1, 1, 1, 0]
"#( SV_PlayerTeam[HP] >=$ 100 )" returns the amount of bits on : 3

So, how do you increase the HP limit? By doing exactly what you suggested ! You take a variable ("VAR_B7_60" is fine for that purpose and completly unused), increment it and heal each time Kuja goes under 10 000 and launch the end of the battle only once it reaches a certain amount.
You also need to set the local variable counter to more than 61 in order to use "VAR_B7_60". That means you must replace the "allocate [NB]" by "allocate 61" in the local variable panel.

To heal, use this line :
set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP])
"FirstOf" converts a list [value1, value2, value3, value4] into value1.
You can also use :
set SV_FunctionEnemy[HP] =$ 65535

You may want to init VAR_B7_60 to 0 in the enemy's initialization function but it's always initialized to 0 by default.

The resultant code should look something like this :
Function func_Trance_Kuja_Loop if ( !VAR_B7_0 ) { set VAR_B7_0 = 1 while ( !( GetData_40 & 8 ) ) { Wait( 1 ) set VAR_B5_206 = GetRandom } set SV_FunctionEnemy[54] =$ 0 while ( GetData_30 != 1 ) { Wait( 1 ) set VAR_B5_206 = GetRandom } RunBattleCode( 35, 0 ) while ( GetData_30 != 4 ) { Wait( 1 ) } } if ( #( SV_FunctionEnemy[HP] <$ 10000 ) ) { if ( VAR_B7_60 < 5 ) { set VAR_B7_60++ set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP]) } else { while ( IsAttacking != 0 ) { Wait( 1 ) } if ( GetData_30 != 4 ) { return } RunBattleCode( 32, 0 ) while ( GetData_30 != 1 ) { Wait( 1 ) } set #( SV_Target = SV_PlayerTeam ) AttackSpecial( 5 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } RunBattleCode( 40, 1 ) set VAR_B5_199 &= 65519 Wait( 1 ) while ( !( VAR_B5_199 & 16 ) ) { Wait( 1 ) } FadeFilter( 0, 1, 0, 255, 255, 255 ) set VAR_B5_199 &= 65519 while ( IsAttacking != 0 ) { Wait( 1 ) } set SV_FunctionEnemy[DEFEATED_ON] =$ 1 RunBattleCode( 33, 5 ) return } } Wait( 1 ) loop

Note that Kuja's counter-attacks are based on his current HP. You may want to change that also. And you may also want not to heal Kuja completly (let's say bring his HP to 55535 instead of 65535) so his Curaga still heals him.
Hope you'll be more at ease after that ^^

Note that if you import the file "LocalVariableSettings_v1.hws" (using Open Mod), the variables of AI scripts will be given names fitting their purpose, so it'll be a bit easier to understand the script.


However, there may be bugs in-game when Grand Lethal hits twice.
EDIT : Nevermind, the bugs are not triggering anymore in the Steam version. That's a good news ^^



So l tried to mess with it but apparently l don't know what l'm doing. :(

l know nothing about script but l understand that l have to restore Ozma HP with the above script about 3 times to simulate 200000 hp so l have to put:


- SV_FunctionEnemy[HP] <$ 20000

- set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP])


somewhere in Function Ozma_Loop and l have to make him secretly restore his HP 3 times so his total HP will become about (45535 *3) + 65535 = 202140

So basically where do l put the above function in his script? Function Ozma_Loop is as follow:




Function Ozma_Loop
if ( !VAR_B6_24 ) {
set VAR_B6_24 = 1
while ( !( GetBattleLoadState & 8 ) ) {
Wait( 1 )
set VAR_B5_206 = GetRandom
}
set SV_FunctionEnemy[SHADOW] =$ 0
while ( !( GetBattleLoadState & 16 ) ) {
Wait( 1 )
set VAR_B5_206 = GetRandom
}
set SV_FunctionEnemy[ATB] =$ ( FirstOf(SV_FunctionEnemy[MAX_ATB]) - 1 )
if ( #( SV_PlayerTeam & 1 ) ) {
if ( ( FirstOf(1[LEVEL]) % 4 ) == 0 ) {
set #( VAR_B14_26 |= 1 )
}
}
if ( #( SV_PlayerTeam & 2 ) ) {
if ( ( FirstOf(2[LEVEL]) % 4 ) == 0 ) {
set #( VAR_B14_26 |= 2 )
}
}
if ( #( SV_PlayerTeam & 4 ) ) {
if ( ( FirstOf(4[LEVEL]) % 4 ) == 0 ) {
set #( VAR_B14_26 |= 4 )
}
}
if ( #( SV_PlayerTeam & 8 ) ) {
if ( ( FirstOf(8[LEVEL]) % 4 ) == 0 ) {
set #( VAR_B14_26 |= 8 )
}
}
if ( #( SV_PlayerTeam & 1 ) ) {
if ( ( FirstOf(1[LEVEL]) % 5 ) == 0 ) {
set #( VAR_B14_28 |= 1 )
}
}
if ( #( SV_PlayerTeam & 2 ) ) {
if ( ( FirstOf(2[LEVEL]) % 5 ) == 0 ) {
set #( VAR_B14_28 |= 2 )
}
}
if ( #( SV_PlayerTeam & 4 ) ) {
if ( ( FirstOf(4[LEVEL]) % 5 ) == 0 ) {
set #( VAR_B14_28 |= 4 )
}
}
if ( #( SV_PlayerTeam & 8 ) ) {
if ( ( FirstOf(8[LEVEL]) % 5 ) == 0 ) {
set #( VAR_B14_28 |= 8 )
}
}
if ( FriendlyMonster_Complete ) {
set SV_FunctionEnemy[ELEMENT_ABSORB] &=$ 65407
set SV_FunctionEnemy[ELEMENT_WEAK] |=$ 128
while ( GetBattleState != 1 ) {
Wait( 1 )
}
BattleDialog( 16 )
Wait( 60 )
}
while ( GetBattleState != 1 ) {
Wait( 1 )
set VAR_B5_206 = GetRandom
}
RunBattleCode( 35, 0 )
while ( GetBattleState != 4 ) {
Wait( 1 )
}
}
switch 3 ( VAR_B6_40 ) from 0 {
case +0:
if ( GetAttacker == SV_FunctionEnemy ) {
set VAR_B6_40 = 1
break
}
break
case +1:
if ( GetAttacker == SV_FunctionEnemy ) {
set VAR_B6_40 = 1
break
}
set VAR_B6_40 = 2
set SV_FunctionEnemy[ATB] =$ ( FirstOf(SV_FunctionEnemy[MAX_ATB]) - 1 )
break
case +2:
if ( GetAttacker == SV_FunctionEnemy ) {
set VAR_B6_40 = 1
} else {
break
}
}
if ( #( SV_FunctionEnemy[HP] <=$ 10000 ) ) {
while ( IsAttacking != 0 ) {
Wait( 1 )
}
if ( GetBattleState != 4 ) {
return
}
RunBattleCode( 32, 0 )
while ( GetBattleState != 1 ) {
Wait( 1 )
}
set #( SV_Target = SV_FunctionEnemy )
RunBattleCode( 36, 9 )
AttackSpecial( 12 )
set SV_FunctionEnemy[STAND_ANIMATION] =$ 1
while ( IsAttacking != 0 ) {
Wait( 1 )
}
set SV_FunctionEnemy[DEFEATED_ON] =$ 1
set VAR_B5_199 |= 8
RunBattleCode( 33, 1 )
return
}
Wait( 1 )
loop



Sorry to bother you again.
Last edited by Agosaxv2.7.2; Aug 8, 2016 @ 12:55pm
Tirlititi Aug 8, 2016 @ 1:14pm 
That looping function for Ozma is split into 3 parts:
1) An initialization thing, which sets up the targets of Holy lvl 4 and Death Lvl 5, and Ozma's elemental absorbtion, and other stuff like that.
2) The system to replenish Ozma's ATB as soon as someone else has an action.
3) The ending move, that launches once Ozma's HP are under 10,000. It triggers Ozma's death animation.

Change that 3rd part into this :
if ( #( SV_FunctionEnemy[HP] <=$ 10000 ) ) { if (VAR_B7_60 < 3) { set SV_FunctionEnemy[HP] =$ FirstOf(SV_FunctionEnemy[MAX_HP]) set VAR_B7_60++ } else { while ( IsAttacking != 0 ) { Wait( 1 ) } if ( GetBattleState != 4 ) { return } RunBattleCode( 32, 0 ) while ( GetBattleState != 1 ) { Wait( 1 ) } set #( SV_Target = SV_FunctionEnemy ) RunBattleCode( 36, 9 ) AttackSpecial( 12 ) set SV_FunctionEnemy[STAND_ANIMATION] =$ 1 while ( IsAttacking != 0 ) { Wait( 1 ) } set SV_FunctionEnemy[DEFEATED_ON] =$ 1 set VAR_B5_199 |= 8 RunBattleCode( 33, 1 ) return } }

Also, before parsing, change the "allocate 60" into "allocate 61" because otherwise VAR_B7_60 is not a valid variable.

However, your calculation is wrong. With the script above, it makes
4*55,536 = 22,2144

There's no need to run the script below 20,000 instead of 10,000, and since Ozma dies under 10,000 HP normally, its default max HP is 55,536 in-game.
Last edited by Tirlititi; Aug 8, 2016 @ 1:15pm
< >
Showing 1-15 of 88 comments
Per page: 15 30 50

All Discussions > Final Fantasy IX > Topic Details