Closed pongstylin closed 5 years ago
Just wondering if we could make an automated tool to make this easier? How have you extracted the Knight animations previously; was it by hand going through each frame or is there a structure to the frame numbering?
I desperately want something more automated, yes. Because the method I used to create the knight animation was painfully manual. Here is what I did in broad strokes:
But, this should not be necessary. I made an attempt to reproduce the assassin animations by ONLY using positioning data from the SWF file (no video captures and comparison). I was partially successful. Enough so, that I know it must be possible. But even then, I manually copied the positioning data from the SWF decompiler application to a JSON file. While this is less painful and quicker than comparing with videos frame-by-frame, there must be a better way. Even so, I was not completely successful. Decompiling an SWF file produces a lot of data and it is difficult to ascertain the meaning of it all.
Do you have the original fla file for the game or the swf file so I can look into automating the extraction process?
Yeah, I asked the same thing earlier. Can you please PM us or let us know how to get ahold of the file?
One thing I'm thinking would be an "easy win" though would be the lighting ward. I'm thinking of tackling that one just using the images I got from the old website.
Latin, I'm not sure how to PM you. But I've updated the first comment with an email address so that you can request the flash files.
Also, it is even easier to implement the Lightning Ward since I've already implemented the lightning strike animation in "The Seed of Chaos". The only other thing that is truly animated may be the bright spot that shows in the midst of the ward as it charges up to strike.
@pongstylin Got your message, thanks!. Will look at the files.
And yes, that is part of what I thought that Lightning Ward is straightforward. I saw the lighting graphics and "The Seed of Chaos" is basically a Lightning Ward with unlimited range. The unit implementation is also easy because there is no movement.
The animation is also done for us on the GIF: https://web.archive.org/web/20150109203347/http://www.tacticsarena.com:80/units/unit.php?name=Lightning+Ward
Of course it sounded like you might be looking for more quality graphics like that but it would at least be a starting point before deep diving into the higher-quality options.
Absolutely. Functionality is most important. I have very high standards, it is true, but I like progress more than perfection.
Alright, getting a hang of this thing...
Some questions, Can you clarify how specifically the coordinates work? E.g.: for the screenshots I have: {x:-2,y:12,c:[{id:133703,x:-15,y:-61},{id:133701,x:-15,y:-61},{id:133702,x:-15,y:-61}]}
So far I have been mostly doing trial and error. It would be nice if the three elements in the array where named parameters (shadow, base, color).
Also, did you have any patterns that you were using for the IDs? (I'm using 1337 for now ^_^)
Finally, do you have some thoughts about coding the sprites? Of course for the sample, I am just using separate images but I'm interested on trying out a sprite as well if there is some basic guidance about how to go about it.
I'm not sure if I have an official point-of-reference for the position offsets that you define in the sprite tuple. I did a lot of trial-and-error when I created the Knight unit. Once we come up with a good way of extracting the information from the original game files, I can start to define these things.
The IDs are based on the original game, but they are not sacred. Semantic names/codes are better than opaque numbers. With that said, there is some automated URL construction for sprite images. So you probably want to use "1360", "1362", and "5589" for the IDs since they match the image names. I'm guessing that "coding the sprites" means you want to try using images extracted from the SWF? Here's the URL. I've enabled browsing of the Lightning Ward directory for your convenience. http://www.taorankings.com/html5/units/5/
P.S. I think I'm assuming some things are obvious when they aren't. So here's an explanation of the coordinates.
{
x:-2, // move the frame left 2 points
y:12, // move the frame down 12 points
c:[
{id:133703,x:-15,y:-61}, // move the sprite within the frame left 15 points, up 61 points
{id:133701,x:-15,y:-61}, // ditto
{id:133702,x:-15,y:-61} // ditto
]
}
Once you implement 3 sprites per frame, it becomes more important that you use the "c" coordinates to line up the sprites so that they fit together perfectly. Once that is done, you can tweak the top-level "x" and "y" to position the unit/frame on the tile correctly.
@pongstylin @latin-programmer I have exported all of the assets from the main SWF. None of the data is structured, however I've made some progress with a 60% manual, 40% automated process. You can download the exported assets here. I will share with you my progress once I've finished testing and implementing the Witch animation.
I have uploaded the compiled HTML5 animations to the arenareborn site. I used an open source tool to export these. The code is really crappy but gives us a way to automate the sprite animations.
1175 1350_Cleric 1353 1356 1357 1358 1359_Egg 1364 1371_LWard 1374_Blank 1397_Shrub 1402 1403_BWard 151_KnightShadow 156_KnightHitArea 1576 1733_Witch 1996 2260_Assassin 2372 237_PyromancerShadow 23_BlackSpike 242_PyromancerHitArea 2475_Enchantress 2646 28 2813_Mud 2957 3088_Stone 3186 3188_Wispet 3271 3418_Ambusher 3501 3648_Frost 36_Bubbles 37 3803 38_Sparkle 3954_Rider 410 4101 42 4244_Dragon 43_Streaks 4481 4712_Furgon 4717_Trophy 4866 5010_Berserker 5163 53 5311_Knight 5313_Tile 5327_Barrier 5328_Die 5357_Damage 5364_Compass 5369_ScoutHitArea 5479_ScoutShadow 5484_ClericHitArea 54_Lightning 5578_ClericShadow 5581_BWardShadow 5583_BWardHitArea 5585_LWardHitArea 5588_LWardShadow 5593_WitchHitArea 55_Randomizer 566_Dspeaker 5675_WitchShadow 567_Pyromancer 5680_AssassinHitArea 57_Fire 5812_AssassinShadow 5817_EnchantressHitArea 5874_EnchantressShadow 5875_Glow 5880_MudHitArea 5935_MudShadow 5940_FrostHitArea 6013_FrostShadow 6014_BigStreaks 6019_StoneHitArea 6053_StoneShadow 6054_Shell 6127_DragonShadow 6132_DragonHitArea 6133_Explode 6134_FireBlast 6139_RiderHitArea 6216_RiderShadow 6217_DspeakerHitArea 6218_DspeakerShadow 6223_FurgonHitArea 6326_FurgonShadow 6351_ShrubShadow 6353_ShrubHitArea 6357_TrophyShadow 6360_TrophyHitArea 6365_WispetHitArea 6410_WispetShadow 6452_AmbusherShadow 6457_AmbusherHitArea 6466 6467_EggShadow 6469 6470_EggHitArea 6535_BerserkerShadow 6540_BerserkerHitArea 6541_Focus 6545 6583_Room 6621 6639 6677 6678 66_Boulder 6708 6758 6772 6779 6796 67_Slash 6801 6831 6836 779 993_Scout
That is good stuff
I played around with those animation pages. I copied 1350_Cleric, decoded the JavaScript so that it was readable, then modified it to add the "red" color. I had some thoughts:
http://www.taorankings.com/html5/cleric.html
I had a go at cleaning up the code more. I've extracted all of what I consider to be data into 3 files:
frames.js
contains each frame's image as a data URIplaces.js
contains positional data about each frame's location within the 129x131 canvasshapes.js
creates img objects for each frame and draws frames to the canvasI'll continue cleaning this code up.
The cleric animation still works, see here https://uat.arenareborn.com/c/.
You're kind of a useful guy to have around, you know that? Removing all that noise was helpful for me to start seeing how things fit together. For example, I notice that the shapes are scaling up the images by roughly 20x. Then, the places are scaling it back down by exactly 20x. I wrote a perl script that went through and modified both places.js and shapes.js so that no scaling was performed at all. And, it still looks the same to my eyes. After doing that, I started to see the coordinates I expected to see based on previous efforts to capture this data from the SWF.
So here's my next big goal and how I'll get there. It'll be tough to pull off, but it seems to be possible:
Step one complete. I'm surprised it was this easy. I figured it would work, but that the shadow would not be positioned where it should underneath the cleric without a lot of manual fiddling. Fortunately, it only required one simple fiddle in step 5. Here is what I did:
So here is the complete cleric animation, with simplified scaling: http://www.taorankings.com/html5/cleric/
Next step, automated data extraction.
So, I've created a simple parser that can be improved, but it's a decent start. Source code is available in this repository. I've currently got two scripts to extract the places and shapes data into two JSON data files.
I am currently in the process of writing a script to take the generated animation files and extract all JSON form them. After this, we will need to find a way to use these JSON files to re-create the animations. @pongstylin Is this something we can work together on?
@Enijar I don't actually plan on parsing JavaScript. The JavaScript can be in any format. It could be packed, unpacked, ugly, beautiful - don't care so long as it works. I can be this flexible because of the technique I'm using to extract the data. Basically I'm going to "mock" the canvas object/context and use dependency injection so that the JavaScript will use my "mock" objects. Instead of the JavaScript drawing to a canvas it will draw to my data gathering code that will write out a JSON file with the compiled data. It should be easier than parsing JavaScript to get the data.
But, even if I was to harvest data from "mock" objects, I have two choices. Either I extract data from the main unit JavaScript and the shadow JavaScript separately and weave the data together. Or, we combine the JavaScript first before I extract all the data at once. The latter approach lends itself to collaboration and is less error prone. You could create a script that helps you to efficiently combine the JavaScript so that it represents a complete unit + shadows. You can easily verify to make sure the combination renders to a canvas correctly by using it in an HTML file. Then, I harvest data from the combined JavaScript using my script. Does this sound like a good plan to you? If you're on board, just send me the combined JavaScript files and I'll do my magic. In the meantime, I'll practice on the combined cleric JavaScript.
I will want one JavaScript file per unit, however. I don't want to mix units together into the same file, because this will complicate data gathering.
When I say parse, I really mean extract. I'm not suggesting we parse the JavaScript at program run-time; I'm suggesting we extract all of the JSON data for each unit's animation as shape and place data frames. We can then build a function/class to take JSON animation data for each unit and render animations from that data.
What I plan on ending up with is something like the following.
Animation (Data)
data/units/cleric.json
Animation (Code)
src/Unit/Cleric.js
There will be a separation between the data and source code.
We can make a canvas for each unit, like you're suggesting, and inject that canvas into a base renderer function/class and render it, like (ctx.drawImage(unitCanvas)
).
I'm not suggesting to mix units into one file, I'm just suggesting an approach of extracting the animation data from the compiled SWF files.
The repository I linked you is still a work in progress and I plan on having it be a complete utility repository to extract all unit animations into their respective JSON file. Finally I plan to have a class to take each unit's JSON file and animate it based on a coherent data structure that can work for all units.
I'm not sure if I'm interpreting what you're saying correctly, but it sounds like an idea that I've considered and discarded as unnecessary. Basically, we don't have to extract data at all. We can use the code generated from the SWF to render to a canvas as a staging area. Then, we use the contents of that canvas as if it was an image and draw it to the game canvas. It is certainly a viable strategy. But if I can extract the data and render it to the game canvas directly without the staging area, then the game would enjoy better performance, which may matter on less powerful mobile devices.
Or, am I totally misunderstanding you? I guess you could clarify exactly what would go into the JSON file and what would go into the JS file and whether we would use the JS file in game or if it would be thrown away once we're done integrating a unit into the game.
My only concern with using the generated code is that it is huge. If you open up the memory profiler in the browser dev tools you will see how much memory is used for just one unit animation. If we use this generated code for each unit (up to 20) this will almost certainly lag any player’s browser.
Essentially what my idea boils down to is extracting the data from the generated animation frames from the SWF decompiler and using that inside the game canvas to render in a loop. This will both improve the readability of animation code and improve rendering performance.
This was just my initial idea but I’m open to having this changed for your idea of using the generated animation code, so long as there is no performance hit to the user’s browser.
Now I think you're misunderstanding me. heh. I'm not suggesting we use the generated animation code. Quite the opposite. Could you give me an illustration of what you intend? Perhaps I can do the same.
Ah, yes I can draw up a graphic tomorrow. Hopefully this will be a clearer representation of what I’m thinking. If you’re not planning on using the generated code then maybe we’re thinking along similar lines but we’re just not explaining to each other well enough.
@Enijar I completed my demonstration. I added a new branch to this repository. Check it out and run these commands:
You will be inundated with data. It is not in JSON format, but then this is just a demonstration of the technique. Notice that it is using a "cleric.html" that came directly from the SWF extractor. I did not need to unpack or beautify it or separate data from code first. It just works. But, it is only extracting data for 2 out of 3 sprites. In order to extract all data for a unit at once, we need to first combine the shadow sprite with the other 2. That is what I was suggesting that you would do to help me out. Again, the code doesn't need to be pretty. It won't make a difference when I run the code to extract the data. I only intend to integrate the JSON file with the data into the game. The code will ultimately be thrown away.
Does this make more sense than my earlier poor explanation?
I think I understand now. Just so I'm getting this correct. I see this as two main things that you want to achieve:
My process is the same but I propose we spend the time extracting a clean, human-readable set of unit animations from the generated code. The repository I linked you yesterday is still a work in progress, but essentially I want to have it do the following (automatically for each unit):
[decompile SWF] -> [extract animation data] -> [render animation data to canvas]
I will then create a generic unit renderer function that can take any unit animation data and render it to the game canvas. I will work on this when I'm back from work tonight, to get you a working version of all unit animations, with much more readable and performant animation code.
I think we're closer to being on the same page. I'll try to clarify further. You described 2 steps, but I'll change it to three.
I demonstrated that S1 was possible with the Cleric. I suggested that you might repeat my success with the other units in default gray formation, but only if you think my approach is the most efficient way forward and wish to help. I think we have the same goals - I'm just trying to convey the approach I'm taking. I am currently working on S2, of which you just saw an early version. The JSON will not end up looking like a series of canvas instructions as it did in my demonstration. S3 will come later, and happens to be step 3 in my "next big goal" post a couple of days ago.
Yes, I think our goals are aligned, just a slightly different way of going about it. I can see step three taking the longest time to implement in the best way but for now I will continue down the route of combining and extracting animation data for each unit. I think my extraction scripts needs more refining but I'm confident I can get most of the grey units extracted with this method. Will keep you updated in this issue thread.
I've updated my branch. The extract.js script now writes clean JSON data without any manual tweaking. I even wrote a "test.html" page that uses the JSON to render the animation on a canvas. But it is just a test page. Step 3 in my next big plan is to render the animation using PIXI code.
I'm not done yet. I need to test to make sure the script can handle the combined cleric+shadows JavaScript. But before I do that, I wanted to give you an opportunity to look over the JSON file to make sure it meets your expectations. I have some comments regarding it:
Ok, so I don't have a lot to do right now. I've already finished upgrading the extractor to support combined unit+shadow animations. In the process, I dumped the "htmlparser" library in favor of the "jsdom" library, which is far more powerful and allows me to accommodate HTML files that take different forms. It's not infinitely flexible, so the extractor could very well need more tweaking once you show me your version of combined animations. My only requirement at this point is that it is an HTML file that renders the animations to a canvas of the correct width and height. Also, the "trim" sprite needs a special color applied to it for me to identify it as special.
But, in any case, I've integrated shadow images and shapes into the JSON file. It even includes the aforementioned "trim" key. At this point, I'm moving on to step 3 in my next big goal. I'm going to try to run the cleric animations on the game board using PIXI rendering.
Just pulled in the extract_unit_data
branch and it looks good. I'll finish up on my version and share with you.
The "Cleric" unit is now integrated into a new "classic" app. I made many other changes as well. I got tired of having so many uncommitted changes, so I pushed them to master. But, I'm still bug hunting.
Glad to hear updates @pongstylin. I've been waiting to see how new the code pans out before I do to many changes in my end as it sounds like the code will look quite different when going in the new direction instead of the sprite/images approach.
I intend a few more tweaks to the JSON unit data. Also, I started work on the extract unit data branch to support extracting special effect data. I'll prioritize finishing both. Once I do, you could make use of my extract script to generate JSON and use the Cleric as an example of how to use it.
Major breakthrough on two fronts:
I've pushed my changes to the "extract_unit_data" branch, but I'm not done yet! The hard part is done, but I've got a couple things I'd like to address before I can let you dig in, @latin-programmer.
After that, the extractor still has room for improvement, but I'll focus on unit integration for now:
Ok, @latin-programmer. I'm done uploading and integrating the JSON files. Pull the latest changes from master.
The "classic" app serves as an example of how to load the JSON files. The "Cleric" serves as an example of how to use the JSON files. You will need to configure the animation offsets on your own. The "src/tactics/core.js" file contains frame offsets for the "Cleric" and you can do something similar for the "Lightning Ward", except you won't need frame offsets for walking or turning since the LW doesn't move. I'm honestly not sure what is included in the LW JSON file. At the very least it will have a frame for just sitting there (stills.S) and a sequence of frames for attacking. But from watching the animation there seems to be something else as well - perhaps a blocking animation? Anyway, if you have any trouble, let me know. Also, there are a lot of TAO YouTube videos out there that might help piece it together. For sure I spent a lot of time with them while integrating the Cleric.
I integrated the assassin unit a few days ago, but it took a few days to come up with a means for triggering a special attack and to implement the assassin bomb special effects. I got a little creative by creating a new triggering mechanism and drawing an original effect. I'm hoping that it is a little more intuitive than it was in the original game, but not too obvious.
The next unit I'm going to conquer is the Pyromancer.
P.S. I also needed to extract the explosion special effect that is used on the tiles surrounding the assassin. While I did so, I upgraded the extractor to support combining multiple special effect layers into one image. It reduced the file size significantly.
Integrated Pyromancer into Classic app. Next is Enchantress.
@pongstylin Just an FYI, I tried running the newer version after merging all changes and it just won't start for me. It "installs" using npm but the start command just hangs. The older version that had "three steps" (install, build, and start) used to work for me.
It could be that the laptop I was using did not have enough juice to run the thing, so I'm setting up another laptop I just got and I'll try again.
It is probably not the laptop. The new method may not be working in your environment. Join our discord chat room and we'll try to figure it out.
@pongstylin you were absolutely right, it was not the laptop, it was the environment (however, that said, maybe was not a bad excuse to try this Alienware laptop I got my hands on recently ^_^).
Am I the only working on Windows 10 here?
The problem is here: NODE_ENV=development webpack --watch --progress --hide-modules & node src/app.js || true
In Windows, NODE_ENV is not a valid command (on Linux) it would be. Without doing anything, it fails with this: 'NODE_ENV' is not recognized as an internal or external command, operable program or batch file.
Then it still starts the app using node but because it doesn't get a chance to build, the classic min file never gets generated.
Found this command: npm install -g win-node-env
After doing that, I no longer error, I get the following output (with parts omitted): webpack is watching the files… […] Asset Size Chunks Chunk Names chaos-app.min.js 38.2 KiB chaos-app [emitted] chaos-app classic-app.min.js 38.6 KiB classic-app [emitted] classic-app faceoff-app.min.js 37.5 KiB faceoff-app [emitted] faceoff-app tactics.min.js 4.62 MiB tactics [emitted] [big] tactics Entrypoint tactics [big] = tactics.min.js Entrypoint faceoff-app = faceoff-app.min.js Entrypoint chaos-app = chaos-app.min.js Entrypoint classic-app = classic-app.min.js
The last line where when it was hanging. My old laptop was using most of the RAM at that point so I figured maybe node was not getting a chance to load. However, on the new laptop, I just cancelled via CTRL+C and then ran: node src/app.js
I could then access the classic app (which what I was trying to do in the first place). So anyhow, long post, hope it helps someone that is trying to get started on the project and facing the same issue. I think the workaround should get me at least where I can start looking into this again.
I develop on Windows 10 with Git Bash and I configure npm to use the bash shell that comes with Git Bash. The README tells you how to configure npm.
@latin-programmer @pongstylin FYI, might want to add cross-env package to the list of dependencies as it makes cross platform ENV settings more easy for devs to work with.
A consistent shell interpreter solves more problems than cross env support
True, but not everyone runs the same shell. Might be worth either extending the readme to include a dedicated "setup" section or implement a cross-platform solution.
@pongstylin Another solution might be to add a Dockerfile. This might actually be useful as the project grows; especially on the server-side of things, with different services running.
It is certainly something to consider
Enchantress complete and pushed to master. It required extra work to completely realize the "focusing" and "paralysis" game mechanics. In other words, I had to manage the breaking of a unit's focus when they are attacked, moved, or turned. Also, units can be paralyzed by one or more units and cannot move, block, or allow another unit to pass by while paralyzed.
Next is scout. This should be the easiest of all the default gray units to implement. The only thing of interest there is the LOS mechanic, about which I am thankfully very familiar.
EDIT: Actually, now that I think about it. I'm not sure if the original game allowed a unit to be paralyzed by more than one unit. Maybe it is only paralyzed by the last unit to paralyze it. If that is true, then you could paralyze your own paralyzed units and then break focus to free them (i.e. because the enemy paralyzer cannot be attacked).
+1 to the idea of being able to have multiple units paralyse a single unit. Regarding the Scout's LOS: do you have the algorithm for it? I could never figure out the formula; just sort of winged it when I played.
Yeah, I think the original game did support a unit being paralyzed by more than one unit. I don't remember ever using or seeing the tactic where you paralyze your own units then defocus to free them. So, it is correctly implemented here as well.
First of all, I'm assuming it IS a formula. I don't think the original game hard-coded the list of tiles that an arrow passes through for every single angle and distance of attack. I could certainly do that, but I don't want to. So, the formula should involve drawing (not visually) a line (this is the LOS/Line-Of-Sight) between the center of the source tile and the target tile and testing if this line intersects any occupied tiles along its path. A common algorithm for this kind of use case is "Ray Casting".
But, based on my experience using the scout, I expect it should not be super precise. That is, it must be closer to the tile's center than just inside its border. So, a formula is not enough. I must determine the size and shape of a given tile's hit area - the area the line/arrow must intersect to be considered a "hit". So, I'll start by using the tile's full size as its hit area. Then, I will experiment by telling the scout to attack various tiles. The LOS algorithm will highlight all the tiles it thinks the arrow passed through. If, based on my experience, I determine it passed through too many tiles, then I will adjust the size of the tile hit area until I get the desired result. The tile hit area itself might not be a diamond, but perhaps a rectangle or oval.
Wish me luck!
The original game had a lot of different kinds of units that each have their own images, animations, and sound effects. The images can be obtained by extracting them from the original game's flash files, but it is harder to string them together into their animations. This is a list of all of the animations for a given unit:
Each frame of each animation typically consists of 3 images: uncolored, colored, and shadow. These 3 images are overlapped to create a complete picture, but must be kept separate since the game is expected to apply a team color to one of them. Ideally, the images should be placed on a spritesheet with a data file that describes how the images should be rendered to reproduce the animations. Rendering information may include the position of each image within the frame or other effects that should be applied to the image (such as rotation, scale, and/or color effects).
If you would like to try to extract this information, please email tactics@dollardns.net so that I can send you the flash files.
This issue will be resolved once we've identified an efficient strategy for extracting animation data.