English (American)  中文(简体)
This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm whether you accept or reject these cookies being set.

A cookie will be stored in your browser regardless of choice to prevent you being asked this question again. You will be able to change your cookie settings at any time using the link in the footer.

by asdfqwe at 8 hours ago
How can I allow heater to turn when he use run attack?

[Image: Capture.png]
by MangaD at 27-06-2020, 12:39 AM
Introduction

In this tutorial, we'll learn how we can change HF's code. Note that, if you want to edit stories, characters and backgrounds, you should check out HF Workshop and tutorials about that specific topic.

This tutorial aims to change other aspects of HF that cannot be changed with HF Workshop. Also, this tutorial assumes that you already have some basic programming knowledge (you know what variables are, conditions, loops...)

Tools

- Flash Player debugger: The Flash Player content debugger is essential if you’re going to modify your SWF. You get a nice error box showing any possible error instead of the player stopping abruptly.

Windows: I'm using version 20.0.0.228 on Windows. Most recent versions (I tried 32) don't work for creating a projector (i.e. exporting EXE in HF Workshop). The file you'll want to use in this case is "flashplayer20_0d0_228_win_sa_debug.exe" (notice the 'sa' that means standalone).

Linux: I'm using version 11.2.202.637, 32-bit, on Linux and it works well (need to install 32-bit libraries though, because no 64-bit version available). Most recent versions (I tried 32) don't work for creating a projector (i.e. exporting "EXE" in HF Workshop).

[Image: flashplayer-debugger.png]

In the above picture you can see that I got an error when trying to run HF in flash player debugger. If it were the normal flash player, the text "Loading images.. Please wait..." would go on forever without warning us of any error.

An ActionScript error has occurred:
SecurityError: Error #2148: SWF file file:///media/Data/HF-LF2/HF/HF_swf/Linux/32-bit/hf+.swf cannot access local resource file:///media/Data/HF-LF2/HF/HF_swf/Linux/32-bit/hf+.swf/[[DYNAMIC]]/17. Only local-with-filesystem and trusted local SWF files may access local resources.
 at flash.display::BitmapData/draw()
 at Data::Global$/PngLoadComplete()

One way to fix this error is to make a projector of HF using the flash player (debugger in our case because we want to debug). This is what we've been doing all along, and knew as "converting swf to exe".

Another way to fix this, is to add the swf as trusted in the Flash Player Settings Manager, as instructed in this web page. Adobe Flash Player Preferences -> Advanced -> Trusted Location Settings... -> Add file... and select your file.

Note: You may need to install Adobe Flash Player NPAPI on your system in order to get its Settings Manager. I checked it on Ubuntu and Arch Linux, but I did not check it on Windows and Mac. If you had to install it on Windows and Mac, please comment below how you did it!

Ubuntu: sudo apt install ubuntu-restricted-extras
Arch Linux: sudo pacman -S flashplugin

Linux users can download HFv0.7+ here, with useful instructions, scripts and libraries in order to run it natively and use flash player debugger.

- Vizzy: Vizzy is a small tool to display the Flash Player logs. You just run the JAR and it shows highlighted real-time logs, allowing you to filter by keywords. You need to have Java installed on your PC in order to run this program.

This is handy when you want to get some values from the SWF at runtime. To see them in the logs, just trace() them (an example will be shown ahead in this tutorial):

findproperty        QName(PackageNamespace(""), "trace")
  ; (push the value to the stack)
callpropvoid        QName(PackageNamespace(""), "trace"), 1

[Image: vizzy.png]

- JPEXS Free Flash Decompiler: The tool that we'll be using to inspect and edit HF's code. You need to have Java installed on your PC in order to run this program. Here is an overview of JPEXS:

[Image: jpexs.png]

After opening HF's swf with JPEXS, on the left pane we are shown all of the swf's resources. Here we're going to look into the scripts only, which is the actual code that does things. HF's code is obfuscated and JPEXS warns of this when we attempt to decompile a script, so go to Settings and tick the checkbox Automatic desobfuscation. You can then click on a script (e.g. Console) to inspect  the code of that class (a term which I assume you're already familiar with, as a programmer). Then, you may click on any piece of AS3 code to see its corresponding p-code on the right pane.

What JPEXS does, is to disassemble the SWF's bytecode (also termed portable code or p-code) into a human-readable form of it, which is an assembly language for the ActionScript Virtual Machine 2 (AVM2). We'll call this assembly language p-code, to be compliant with JPEXS. P-code is still quite low level language and hard to understand, which is why JPEXS decompiles it into Action Script 3, an high level programming language and the one that Marti programed in.

However, JPEXS is not fully capable of converting AS3 into p-code (at the moment of this writing), so trying to edit the AS3 will break the game. Our only option is to edit the p-code, and being thankful that we can see the AS3 that it corresponds to.

However, I don't recommend editing the p-code directly with JPEXS, because we cannot keep track of our modifications in the code that way. So I recommend you to use RABCDAsm.

- RABCDAsm (Robust ActionScript Bytecode (Dis-) Assembler): This is an SWF disassembler that will extract the p-code into multiple files. This way, you can edit the code, comment the code, and keep track of your changes. RABCDAsm's syntax is a bit different than JPEXS, but the logic is the same.

These are the batch scripts I use to decompile and compile, respectively:

-- Decompress the SWF file (if it is compressed...)
RABCDAsm_v1.18/swfdecompress.exe hf_v0.7+.swf

-- Extract ABC blocks from an SWF file
RABCDAsm_v1.18/abcexport.exe hf_v0.7+.swf

-- Disassemble the ABC blocks into a well structured assembly language
RABCDAsm_v1.18/rabcdasm.exe hf_v0.7+-0.abc
RABCDAsm_v1.18/rabcdasm.exe hf_v0.7+-1.abc

-- Assemble the ABC blocks
RABCDAsm_v1.18/rabcasm.exe "hf_v0.7+-0"/"hf_v0.7+-0".main.asasm
RABCDAsm_v1.18/rabcasm.exe "hf_v0.7+-1"/"hf_v0.7+-1".main.asasm

-- Replace the old ABC blocks in the SWF with the new ones
RABCDAsm_v1.18/abcreplace.exe "hf_v0.7+.swf" 0 "hf_v0.7+-0"/"hf_v0.7+-0".main.abc
RABCDAsm_v1.18/abcreplace.exe "hf_v0.7+.swf" 1 "hf_v0.7+-1"/"hf_v0.7+-1".main.abc

pause

Documentation

Before we start, I leave here some useful links that you'll need for your p-code learning journey. The first has a list of all the p-code instructions, and the second is a much more extensive document that may help understand some concept where you may get stuck and / or is not present in the first document.

List of all AVM2 instructions:
- https://www.free-decompiler.com/flash/do...ns.en.html
- https://konghack.com/content/19-avm2instructions

ActionScript Virtual Machine 2 (AVM2) Overview: https://www.adobe.com/content/dam/acom/e...erview.pdf

RABCDAsm Syntax: https://github.com/CyberShadow/RABCDAsm#syntax

ActionScript 3.0 Bible: https://books.google.pt/books?id=6uhORLiHHiwC

How memory works

Before jumping head-on into the code, it is important to understand how memory works. In particular, the stack. The stack has two important operations: push and pop. The following picture illustrates this.

[Image: Lifo_stack.png]

For p-code, we have an evaluation stack (or operand stack) that, as the name implies, is used for performing evaluations (arithmetic calculations, loading and storing data through references, etc).

AVM2 Overview (Chapter 2.2) Wrote:The local data area for the method consists of the operand stack, the scope stack, and the local registers.

- The operand stack holds operands for the instructions and receives their results. Arguments are pushed onto the stack top or popped off the stack top. The top element always has address 0; the one below it has address 1, and so on. Stack addresses are not used except as a specification mechanism.
- The scope stack is part of the run-time environment and holds objects that are to be searched by the AVM2 when an instruction is executed that calls for name lookup. Instructions push elements onto the scope stack as part of the implementation of exception handling, closure creation, and for the ActionScript 3.0 with statement.
- The local registers hold parameter values, local variables in some cases, and temporaries.

Understanding by example

Here is an AS3 method example taken from Data.Global. By the way, Data is the package, Global is the class. You should understand these concepts if you are familiar with Object Oriented Programming (OOP).

I chose this function, PremiumLink, because it is relatively short, but employs arithmetic instructions among other things that are worth checking out.

// Link for buying premium account
public static function PremiumLink(param1:String) : String
{
    // Do nothing if we are not logged in or are playing try mode
    if(!Global.logged_in || Global.trial)
    {
          return "";
    }
    var _loc2_:int = Math.floor(Math.random() * 9999);
    var _loc3_:int = Global.ac_id * 3 + 297 + Math.floor(_loc2_ / 5);
    var _loc4_:int = Global.ac_id % 300 * 71 + Global.ac_id % 181;
    var _loc5_:int = Global.ac_id % 100 * 47 + Global.ac_id % 269;
    var _loc6_:String = encodeURIComponent(Global.ac_email);
    var _loc7_:String = "http://s.herofighter.com/premium?l=" + param1 + "&check=" + _loc2_ + "&session=" + _loc3_ + "&pid=" + _loc4_ + "&t=" + _loc5_ + "&em=" + _loc6_;
    trace(_loc7_);
    return _loc7_;
}

And here is the corresponding p-code:

JPEXS:
trait method Qname(PackageNamespace(""),"PremiumLink")
flag FINAL
dispid 68
method
name null
param Qname(PackageNamespace(""),"String")
returns Qname(PackageNamespace(""),"String")

body
maxstack 4
localcount 8
initscopedepth 3
maxscopedepth 4

code
getlocal_0
pushscope
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"logged_in")
not
dup
iftrue ofs0016
pop
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"trial")
convert_b
ofs0016:iffalse ofs001d
pushstring ""
returnvalue
ofs001d:getlex Qname(PackageNamespace(""),"Math")
getlex Qname(PackageNamespace(""),"Math")
callproperty Qname(PackageNamespace(""),"random") 0
pushshort 9999
multiply
callproperty Qname(PackageNamespace(""),"floor") 1
convert_i
setlocal_2
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_id")
pushbyte 3
multiply
pushshort 297
add
getlex Qname(PackageNamespace(""),"Math")
getlocal_2
pushbyte 5
divide
callproperty Qname(PackageNamespace(""),"floor") 1
add
convert_i
setlocal_3
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_id")
pushshort 300
modulo
pushbyte 71
multiply
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_id")
pushshort 181
modulo
add
convert_i
setlocal 4
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_id")
pushbyte 100
modulo
pushbyte 47
multiply
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_id")
pushshort 269
modulo
add
convert_i
setlocal 5
findpropstrict Qname(PackageNamespace(""),"encodeURIComponent")
getlex Qname(PackageNamespace("Data"),"Global")
getproperty Qname(PackageNamespace(""),"ac_email")
callproperty Qname(PackageNamespace(""),"encodeURIComponent") 1
coerce_s
setlocal 6
pushstring "http://s.herofighter.com/premium?l="
getlocal_1
add
pushstring "&check="
add
getlocal_2
add
pushstring "&session="
add
getlocal_3
add
pushstring "&pid="
add
getlocal 4
add
pushstring "&t="
add
getlocal 5
add
pushstring "&em="
add
getlocal 6
add
coerce_s
setlocal 7
findpropstrict Qname(PackageNamespace(""),"trace")
getlocal 7
callpropvoid Qname(PackageNamespace(""),"trace") 1
getlocal 7
returnvalue
end ; code
end ; body
end ; method
end ; trait

RABCDAsm:
 trait method QName(PackageNamespace(""), "PremiumLink") flag FINAL dispid 68
  method
  refid "Data:Global/class/PremiumLink"
  param QName(PackageNamespace(""), "String")
  returns QName(PackageNamespace(""), "String")
  body
    maxstack 4
    localcount 8
    initscopedepth 3
    maxscopedepth 4
    code
    getlocal0
    pushscope

    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "logged_in")
    not
    dup
    iftrue              L11

    pop
    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "trial")
    convert_b
L11:
    iffalse            L14

    pushstring          ""
    returnvalue

L14:
    getlex              QName(PackageNamespace(""), "Math")
    getlex              QName(PackageNamespace(""), "Math")
    callproperty        QName(PackageNamespace(""), "random"), 0
    pushshort          9999
    multiply
    callproperty        QName(PackageNamespace(""), "floor"), 1
    convert_i
    setlocal2

    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_id")
    pushbyte            3
    multiply
    pushshort          297
    add
    getlex              QName(PackageNamespace(""), "Math")
    getlocal2
    pushbyte            5
    divide
    callproperty        QName(PackageNamespace(""), "floor"), 1
    add
    convert_i
    setlocal3

    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_id")
    pushshort          300
    modulo
    pushbyte            71
    multiply
    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_id")
    pushshort          181
    modulo
    add
    convert_i
    setlocal            4

    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_id")
    pushbyte            100
    modulo
    pushbyte            47
    multiply
    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_id")
    pushshort          269
    modulo
    add
    convert_i
    setlocal            5

    findpropstrict      QName(PackageNamespace(""), "encodeURIComponent")
    getlex              QName(PackageNamespace("Data"), "Global")
    getproperty        QName(PackageNamespace(""), "ac_email")
    callproperty        QName(PackageNamespace(""), "encodeURIComponent"), 1
    coerce_s
    setlocal            6

    pushstring          "http://s.herofighter.com/premium?l="
    getlocal1
    add
    pushstring          "&check="
    add
    getlocal2
    add
    pushstring          "&session="
    add
    getlocal3
    add
    pushstring          "&pid="
    add
    getlocal            4
    add
    pushstring          "&t="
    add
    getlocal            5
    add
    pushstring          "&em="
    add
    getlocal            6
    add
    coerce_s
    setlocal            7

    findpropstrict      QName(PackageNamespace(""), "trace")
    getlocal            7
    callpropvoid        QName(PackageNamespace(""), "trace"), 1

    getlocal            7
    returnvalue
    end ; code
  end ; body
  end ; method
 end ; trait

All right, let's get to the code! We'll start by analyzing the first 15 lines (From JPEXS).

The first 3 lines belong to the method's declaration.

trait method Qname(PackageNamespace(""),"PremiumLink")
We cannot find trait in the list of AVM2 instructions, but we can find it in chapter 4.8 of the AVM2 Overview. Without getting into much detail and unnecessary complications, the 1st line is declaring our method and specifying its name. If we wanted to start a new method in this class, we would copy this code and change the name of the method here. Notice that, we need an end tag at the end of our p-code to close the trait (similar to brackets in programming Slight smile)

flag FINAL
This one is an interesting one, because it is not shown in the AS3 code. This means that the method is final (i.e. cannot be overridden in a child class). It is explained in chapter 4.8.6. This line is not really necessary to be here, but since it is, we should leave it.

dispid 68
Quoting chapter 4.8.5:
AVM2 Overview Wrote:The disp_id field is a compiler assigned integer that is used by the AVM2 to optimize the resolution of virtual function calls. An overridden method must have the same disp_id as that of the method in the base class. A value of zero disables this optimization.
Therefore, unless you want to override a base class' method, you should not worry about this id. If you are creating a new method, you should give it a number that does not exist in any other method in this class (Data.Global in this case), or zero. This line must exist though.


The next 4 lines start the method's definition.

method
name null
RABCDAsm README Wrote:method blocks may contain one name field (multiname), a refid field, param fields (multinames - this represents the parameter types), one returns field (multiname), flag fields (NEED_ARGUMENTS / NEED_ACTIVATION / NEED_REST / HAS_OPTIONAL / SET_DXNS / HAS_PARAM_NAMES), optional fields (values), paramname fields (strings), and a body field (method body).
This is starting our method's definition. Notice that it needs an end instruction at the end, just like trait. In JPEXS, the refid isn't shown to us, and name is set to null since it does not exist.

param Qname(PackageNamespace(""),"String")
This line says that our method receives one parameter of type String. We could have more lines like this for more parameters.

returns Qname(PackageNamespace(""),"String")
This line says that our method returns an object of type String. If we wanted to return void, we'd have returns Qname(PackageNamespace(""),"void"). This line is necessary to have and only one can exist.



Let's now look at the next 5 lines of code, which give us information about the method's body. To understand where I got the information for the following lines, check chapter 4.11 of the AVM2 Overview.

body
Starting the body segment.
RABCDAsm README Wrote:body blocks - always declared inline of their method block - must contain the maxstack, localcount, initscopedepth and maxscopedepth fields (unsigned integers), and a code field. It may also contain try and trait fields.
AVM2 Overview (chapter 3.3.3) Wrote:When control enters a method, three local data areas are allocated for it, as outlined in Chapter 2—an operand stack segment, a scope stack segment, and a set of local registers. The operand stack segment holds operands to the instructions; values are pushed onto that stack or popped off it by most instructions. The scope stack segments holds scope objects in which the virtual machine will look up names at execution time. Objects are pushed onto the scope stack by OP_pushscope and OP_pushwith, and popped off by OP_popscope and by the exception handling machinery. The local registers hold parameter values, local variables, and temporaries.
On method entry, the state of these data areas is as follows.
  • The operand stack is empty and has room for method_body_info.max_stack values.
  • The scope stack is empty and has room for method_body_info.max_scope_stack values.
  • There are method_body_info.local_count registers.
  • Register 0 holds the “this” object. This value is never null.

maxstack 4
This is a complicated one. This one is "the maximum number of evaluation stack slots used at any point during the execution of this body". In other words, how many values can be on the operand stack at once. To calculate this, we have to go through each instruction (starting after code) one by one and emulate the stack to see the most number of objects it can contain at once, with the help of the "AVM2 Instruction list" link provided above, that shows us how the stack is before and after a specific instruction.

The good news is that you do not have to give a correct value here, you can give an higher value than it actually is (but not too big) and it shouldn't cause any problems. But if you specify a smaller value than the correct one you may get a stack overflow like this guy did. For small functions calculating the maxstack is easy, but for big functions, I suggest try a value until it works. Smile

I have made a picture to illustrate how to calculate the maxstack for this guy's example. Notice that there is more code before and after that was ommited.

[Image: maxstack.gif]

localcount 8
This one is easy. This is the index of the highest-numbered local register this method will use, plus one. We set and get register values using setlocal and getlocal instructions. We find in this code that the highest index is 7, because of setlocal 7 and getlocal 7, therefore our local count is 7+1=8.

initscopedepth 3
We've seen the operand stack in maxstack. But initiscopedepth is about the scope stack. The first scope is the global scope. In this method's example, we are inside the Data.Global class, also, this method is static. The second scope is the static instance of the class. And finally, the third scope is the method itself. Hence, initscopedepth is 3.

Notice that if the Data.Global class extended any other class, then we could have more scopes (accounting for all the base classes in the chain).

Notice also, that if the method were not static, then we'd have also the scope of the object in which we're calling the method, and initscopedepth would be 4.

My advice for knowing what value this instruction should be given, is to look at the other methods in the class and use the value that they're using, taking into account if they are static or not.

The Scope Chain:
- https://books.google.pt/books?id=6uhORLiHHiwC&pg=PA13
- https://docstore.mik.ua/orelly/web2/action/ch09_07.htm

maxscopedepth 4
Note: In ActionScript, local variables are function-local and not block-locals, just like JavaScript's variables declared with var.
AVM2 Overview (chapter 2.2, page 8) Wrote:Instructions push elements onto the scope stack as part of the implementation of exception handling, closure creation, and for the ActionScript 3.0 with statement.



TO CONTINUE SOON...
by MangaD at 21-06-2020, 03:43 PM
After talking with @不饿白帝BEBD, @Runningcake and @Lee Catalpa Tien (on QQ), I have grasped a few concepts of the SPT file. I'll leave a rough draft here, for future updates and development. Feel free to comment with your knowledge and improve it!

Overview of SPT file:

[Image: spt.png]
  • cad and keyTgr arrays appear to be useless. Maybe leftovers from older versions.
  • frames array contains all the frames' data.
    • allowKeyTrigger attribute of a frame specifies whether or not we can perform an action (e.g. attack) from this frame.
  • action array contains all the possible actions of a character.
    • a_keyTgr specifies the actions that can be TriGgeRed in the action state that it belongs to, as well as how to trigger them
      • rkt (restrict key trigger) if it is false, allowKeyTrigger in frame is ignored. If it is true, it can be triggered only in frames of the action that have allowKeyTrigger set to true. Example: In STAND state, rkt should be false for a special move if we want to trigger it at any time. But during an attack or specific action, rkt should be true so that only in certain frames (e.g. later frames) of the attack we can trigger another one.
      • k key combination to perform move to right.
      • kr key combination to perform move to left.
      • agi is the Action Group Index that this trigger corresponds to.
      • ati - in an action group there is an actionIndex array, ati is the index in this array.
      • ai - each element of the actionIndex array is also an array, and ai is the index of the element in this array that we want.
  • actionGroup - corresponds to a set of actions. Most common are normal, gua (guard), cat (catch human), hea (catch object), rid (ride)
    • action useless array, probably leftover from older versions. It was moved to the outside action array mentioned above. Now actionGroup is linked to a set of actions through the attributes ati, agi and ai inside a_keyTgr inside of an action.

Making new moves requires modifying actionGroup, action and frame arrays.
  • hire_id - array with type of soldiers for each possible unit (we have to use their id to get them, e.g.: "z_infantry01"). If you use a mount as id (e.g. horse, horse_monster, horse_triceratops), they will be available to mount but won't attack on their own.
  • hire_max - array with count of soldiers for each possible unit (for ex. lets say "z_infantry01" if we put its value to 1, then we get 4 soldiers, if 2 means then 8 soldiers)
  • hire_price - array with costs to hire a unit, for each possible unit.
  • hire_h - array with mounts for each possible unit. If "null" they are on foot. If "h" they are on horse. If "m" they are on monster. If "t" they are on triceratops. These mounts won't disappear when a soldier falls from them, they will be available to mount by the player or another soldier, except if the hire_id is z_cavalry01, then the mounts will disappear.
by MangaD at 21-06-2020, 11:33 AM
In HF v0.7, Raye's lightning attack (G → A)  cannot turn around.

[Image: raye2.png]

In this tutorial, we will be fixing that!

I will assume that the reader is already familiar with the HF Workshop tool, if he is not, he is advised to go to the linked thread, read it and watch the tutorial video.

Ok, so, the file where the data that we need to change is, is called Spt file. In HF v0.7, Raye's Spt file has ID=316. Extract that file with HFW, extract the JSON file from the ZIP file, and open it with your favorite text/code editor. This file is huge, so I recommend you to collapse all attributes in order to get an overview of the file's contents, as shown in the image:

[Image: spt.png]

Here I'm using VS Code, and the shortcut to fold all is Ctrl+K Ctrl+0.

You can see in the image the lines that have an arrow and are highlighted in blue, these are arrays and you can expand to view, in case you have collapsed them as suggested. Our relevant data for this tutorial is in the action array (line 2346), which contains all of the actions related to our character.

The property we need to change for our action is called allowTurnFace, and we should set it to true. The problem now is finding which action corresponds to Raye's lightning move.

Update: The array keyTgr is actually useless, gem from older versions, and the below explanation only works by chance. The actual way to find the action we want is more complicated, and this thread can shed some light.

The array keyTgr contains the key sequences for performing our actions. We know as a matter of fact that our sequence is G → A, and that is translated to gra (guard, right, attack) in the keys property. We find gra in position 2 and 6 of the keyTgr array (lines 891 and 951), and they are associated to actions attack4 and attack3 respectively, so now we know what actions to look for.

Going back to our action array (line 2346), we will now search in it for the string array4. In VS Code, you can select the collapsed array, then press Ctrl+F, and select the icon to Find in selection (Alt + L), as shown in the picture below.

[Image: spt2.png]

The first action we found is in the 75th position of the action array, and has name _RID_ATTACK4. Because it has the _RID_ prefix, this probably refers to the attack while riding, which is not what we're looking for right now, so we press the down arrow or enter to keep looking. We then find the action with name ATTACK4 in position 82, this is what we want, and we change the attribute allowTurnFace to true.

"82": {
    "HFW_classNameXXX": "Data.Action",
    "allowTurnFace": true,
    "selfLoop": false,
    "nextAgi": -1.0,
    "aiz2": 0.0,
    "aix1b": 0.0,
    "landAti": -1.0,
    "frame": null,
    "mpBurn": 0.0,
    "nextAti": -1.0,
    "landActionName": null,
    "aix2b": 0.0,
    "landAgi": -1.0,
    "name": "ATTACK4",
    "nextActionName": null,
    "special": -1.0,
    "aix2": 0.0,
    "aiz1": 0.0,
    "index": 82.0,
    "nextAi": -1.0,
    "aix1": 0.0,
    "a_keyTgr": {
        "HFW_ArrayLenXXX": 1,
        "0": {
            "HFW_classNameXXX": "Data.A_KeyTgr",
            "kl": 1.0,
            "ati": 3.0,
            "pollhp": 0.0,
            "agi": 0.0,
            "atf": false,
            "ai": 2.0,
            "k": "a",
            "hp": 0.0,
            "rkt": true,
            "mp": 0.0,
            "kr": "a"
        }
    },
    "type": -1.0,
    "landAi": -1.0,
    "frameIndex": 222.0
},

After trial and error attempts, I found that changing this property only for attack4 was enough, and no need to change it for attack3. I have not spent enough time figuring out the difference between these 2 attacks, but this completes our tutorial. All you have to do next is to replace the JSON file in the ZIP file, replace it in HFW, and export  the new EXE. Cheers!



Special thanks to @不饿白帝BEBD, @Runningcake and @Lee Catalpa Tien
by MangaD at 19-06-2020, 08:49 PM
I will assume that the reader is already familiar with the HF Workshop tool, if he is not, he is advised to go to the linked thread, read it and watch the tutorial video.

Frames' data can be found in SPT files. Drew's SPT file has id=229. Open that file and search, for example, for "name": "JUMP_ATTACK" in order to change Drew's jump kick. You should find the following:

"50": {
    "HFW_classNameXXX": "Data.Action",
    "selfLoop": false,
    "nextAi": -1.0,
    "allowTurnFace": false,
    "nextAti": -1.0,
    "frame": null,
    "aiz2": 0.0,
    "special": -1.0,
    "landAti": -1.0,
    "landAi": -1.0,
    "nextAgi": -1.0,
    "name": "JUMP_ATTACK",
    "a_keyTgr": null,
    "landAgi": -1.0,
    "frameIndex": 146.0,
    "aix1b": 0.0,
    "mpBurn": 0.0,
    "index": 50.0,
    "aix2": 0.0,
    "aiz1": 0.0,
    "nextActionName": null,
    "type": -1.0,
    "aix2b": 0.0,
    "aix1": 0.0,
    "landActionName": null
},

Notice that frameIndex is 146, that's the frame where JUMP_ATTACK begins. Now you can search for "146" and you'll find that frame. All subsequent frames with "name": "jump_attack" belong to that motion. Every frame has a duration attribute, you can increase it to slow down the motion. Good luck!
  • 1(current)
  • 2
  • 3
  • 4
  • 5
  • 40
  • Next 
Welcome, Guest
You have to register before you can post on our site.
Username/Email:

Password


Search Forums

Forum Statistics
Members: 212
Latest member: King
Forum threads: 263
Forum posts: 1,282
Online Users
There are currently 33 online users.
0 Member(s) | 32 Guest(s)
Bing
Upcoming Events
No upcoming events