Introduction
Anyone who enjoys programming while playing a game always wants to screw something of their own or make something useful. Someone makes modifications to the game, someone trainers or cheats, someone wiki or fan sites. Not spared this passion party and me.
I play at the moment in MMO Aion: Tower of Eternety (hereinafter, for simplicity, “Aion”) from the Korean developer NCSOFT. It is about her and go talk.
The first thing that comes to mind regarding MMOs is user interface plugins. Unfortunately, although the game supports add-ons, it’s impossible to create them for a mere mortal: there is no API documentation, and the client simply ignores them without a digital signature. A very strange decision of the developers, but apparently, they have their own reasons.
The second is the change in the appearance of the game items. There seems to be no problems here, except that modification of the client is prohibited by the user agreement and that each user will have to install this modification. In this way, you can “try on” the appearance of new items that are only on Korean servers for now. Again does not fit.
')
The third is various cheating devices. To the credit of the developers, something really worthless simply by modifying the resources of the game cannot be obtained, everything is checked by the server and the tsiferka client rather for user reference and synchronization of actions on the screen with calculations on the server. You can indulge in an increase in the size of any inconspicuous objects in the game, such as treasure chests or named monsters. But it is fraught with glitches and again violates the PS as well as item 2.
It remains to create out of the game, but on her topic, but we, of course, are interested in what can be coded, and not in fanarty or articles. There are plenty of databases of varying degrees of relevance and scall on the Internet. But what really did not exist before me was the character's calculator, at least I did not come across the online version. With the introduction of new classes in the game, the existing skill calculators (stigmas) are also irrelevant. The equipment calculator is complicated enough to start analyzing it, so let's talk about skills first.
Determination of requirements
So, a calculator was chosen as a project. The first thing to decide in this case is the development tools. The online focus of the game immediately cuts off the offline version of the application and implies a web, and I wanted to learn some experience in web programming, the benefit of the time was enough to sit and figure out, no bosses were in a hurry. Partly because of this, it all started with the choice of tools.
For the creation we need:
- Game client and tools for unpacking resources
- Favorite programming language for parsing and converting game data locally on your computer. Personally, I prefer Perl.
- Intermediate proficiency in HTML + CSS and JavaScript (jQuery). Server programming is not necessary due to the simplicity of the calculator.
- A simple graphic editor for processing images of the interface from the game, for example Paint.NET.
- Batch image converter from dds to png to convert skill icons. For example, ImageConverter + (enough of the trial, it does not put watermarks on images of size 40x40)
- Overcome laziness. Although, perhaps, this is the main point, not the last.
Starting any development is always worth analyzing the subject area. Let's see what are the skills in the game.
The skills in the game are divided into 2 categories: those studied from books and those obtained by the installation of stigmas. Stigma, in turn, are divided into 2 types: normal and improved. Book skills are the same for all characters of one class, stigmas are usually strong skills, but they have a restriction on their use - a fixed number of slots for them. Stigma and equipment completely determine the role of the character in the game and the group, the so-called “build” (from the English build - build) character. Simple stigmas can be installed in any free slot, improved only in the slot for improved and require the presence of 1 or 2 other stigmas, thus forming a skill tree.

Stigma and a slice of my character's skill window
It is clear that there are fewer stigma slots than are available in the game and it would be nice to be able to conveniently calculate suitable options, despite the wretchedness of the balance in Korean MMO, there are still a lot of them. It is her calculator and provide.
Total skills:
- Each class has 2 trees of improved stigma.
- The number of slots increases with increasing level to a maximum of 6 pieces of each type.
- The level of stigma can also increase with the growth of the character.
- Each stigma has a minimum level of character.
- Some stigmas are different for different races, either by name and icon or effect.
- Each stigma has an “insert” value in stigma fragments.
- Improved stigmas have an Abyss Points purchase price (AP, Abyss Points, a type of game currency associated with PvP).
Also, the final calculator should have the following functions:
- The possibility of a set of stigmas for each of the game classes of any level from 20 (the first slot opens) to the maximum.
- The choice of stigm level.
- Ability to share the set with friends or in discussion forums. This is a very important part of calculators.
- Multilingual, I was interested in receiving feedback from foreign players.
Database
With the requirements and the subject area decided. You can begin to study the material with which you have to work.
The game client is written on the CryEngine 1 engine, so there are enough unpacking utilities on the Internet. I will not discuss this point in detail here, there is enough material for one more article. Even models and character animations were able to get out of the game.
All digital data on objects and skills are stored in simple XML with encoding UTF16-LE, graphics in DDS (DirectDraw Surface - DirectX format). Respect and respect for the developers for not hiding data behind seven locks with encryption and using human formats.
Almost all the names of fields in XML correspond to their purpose and a lot of time to figure out what to spend is not necessary.
Information on stigmas is stored in files with items, the format is uniform for all items, so there is a lot of excess garbage. The fields containing the data necessary for the calculator look like this:
<client_item> ... <abyss_point>76350</abyss_point> ... <priest>55</priest> ... <require_skill1>PR_EternalServent</require_skill1> <require_skill2>PR_SufferMemory</require_skill2> <require_shard>275</require_shard> <stigma_type>Enhanced1</stigma_type> <gain_skill1>PR_CallLightning_G1</gain_skill1> … <race_permitted>pc_light pc_dark</race_permitted> </client_item>
Above is an improved level 55 stigma for a healer for 76350 AP giving the CallLightning (Thunder) skill of the first level. The installation requires 275 stigma shards, the already established EternalServent (Holy Energy) and SufferMemory stigmas (Weakening stigma) and is available to characters of both races.
The name of the skill consists of the class prefix - “PR” (priest), the devname of the skill - “CallLightning” and the skill level - “G1” (grade 1). For the necessary stigmas, the level is not important, therefore it is not indicated, in fact, this is not a title of a skill at all, but a category. Details about the description of skills will be discussed closer to the end of development. This information is enough to build a tree and in general there is everything necessary except for the description of the skills themselves and their icons, we will return to them later.
Using a small script, we obtain from the XML file the first version of the JSON base for the calculator in approximately the following form:
var stigmas = { 'priest': { ... "pr_calllightning":{levels:[55,58,61,64],type:2,require:['pr_eternalservent','pr_suffermemory'],shards:[275,290,732,768],abyss:[76350,91800,187950,205450]}, ...
For each class, abilities are listed as properties, in which, in turn, properties are given levels, the corresponding number of fragments and AP, as well as the necessary stigmas. All that is specified in the XML above. Data storage in JSON greatly simplifies working with them. You shouldn’t push for minimization of the names of the properties of objects - it will reduce their compression without us, but it will not be very convenient to work. If there is an overwhelming desire, then the name can be reduced to 1-2 letters, but better at the end of development.
The file with all the skills turned out to be a little less than 20kb (to the final version it has grown to 45kb) and it will be noticeably stung by a deflate, stigmas are rarely updated (every large patch), and therefore there is no point in loading data using AJAX and you can store it statically in a separate js file. It is also not necessary to sculpt all static databases into one file, this makes it difficult to update them.
In the case of objects such a trick, of course, would not have passed. Their total number exceeds 50,000 and the organization of the search in such a dump would be extremely inefficient. Without a query to the database in such cases is not enough. You should always choose a storage method appropriate to the amount of data and operations that will be performed on them. Also, you should always choose a format that does not set a rigid framework, it often happens that you need to make changes that were not originally planned and still lie somewhere in the depths of the developers' minds. Try to leave room for painless insertion of “crutches”.
Calculator outline
Here we have a base with the minimum necessary information, we can begin to make sketches. With the help of game graphics that we have in the client, we make a draft version of the interface. At first, this can be done very schematically: with blocks with a frame and without graphics. Being engaged in visualization at the initial stages of development, you can immediately identify some shortcomings in database design, if any, for example, a missing property or an inconvenient presentation format. Also appears the first information about the convenience or convenience of working with a calculator.

Draft Interface Sketch
Most of the work area is occupied by stigma slots as the main element. Nearby are the available regular stigmas and the bottom is an empty space for trees of improved stigma. The skill trees have yet to be drawn as well as choosing a level with a class, but for the time being it is enough that you can set the missing values ​​hard in the code. The list of available stigmas is formed according to the residual principle: everything that is not inserted into the slots is accessible.
Already at the earliest stage it is worth getting rid of the magic numbers and variables as much as possible. I brought everything to the Calc container object, and the functions will be stored there:
Calc = { className:'priest', race:'pc_light', level:65, slots:[], norAllowed: 6, advAllowed: 6, norLevel:[20,20,30,40,50,55], advLevel:[45,45,50,52,55,58], SLOTS_NOR_MAX:6, SLOTS_ADV_MAX:6, MIN_CHAR_LEVEL: 20, MAX_CHAR_LEVEL: 65 }
The norAllowed and advAllowed values ​​store the value of the available regular and improved slots, respectively, they are calculated from the character level level based on the norLevel and advLevel arrays containing slots appearance levels.
For storing inserted stigmas, in my opinion, the array will be the most optimal data structure. It is not rational to contain two arrays, this makes it difficult to iterate over the slots and sometimes it can be reduced to 1 cycle instead of 2x. For example, the function checking whether a certain stigma is inserted or not, it does not need to know whether the 4th slot of improved stigma is available or not and just runs through the whole array, the removal function should take care of clearing it.
Using variables instead of numbers makes it easy to vary the total number of slots without changing the logic of the functions. So in my case, everyday stigmas are stored in the array under the indices from 0 to SLOTS_NOR_MAX - 1, and improved from SLOTS_NOR_MAX to SLOTS_NOR_MAX + SLOTS_ADV_MAX - 1. Thus, if all of a sudden the slots become 8 instead of 6, you only need to add them in the interface and set the values ​​for SLOTST_TIV_NT_TOR__XXX_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXF and SLOTS_ADV_MAX - everything else will work with the new values ​​as if it always was.
Available stigmas are drawn as blocks containing the name of the stigma in the identifier. This allows you to use one handler for all stigmas and identify the desired stigma by the object identifier of the event that caused the event. The skill level is automatically set to the maximum for the selected character level. It is unlikely that someone is interested in lower levels and there is nothing to force the user to make a couple of clicks to change to the maximum. For the same reason, the level of the character is initially also worth the maximum possible. A good interface should be designed so that the most frequent actions are the simplest and lead to the expected result.
Most users only view the finished options made up by other users and therefore it is better to unload the interface with such functions as level selection and deletion. It is better to show them when you hover on exposed stigma.

Here is the version that you can try yourself. But this is only the beginning.
Skill trees
When building trees, a problem quickly emerged: some skills belong only to one of the races and improved stigma requires the installation of any of them. Some of these skills also had different names. There are several alternative solutions: add a property with a category and compare it according to it, rather than by name or list several options in require. As a result, I settled on a mixed solution: I added a light or dark suffix to all names of race-specific skills and I added both skill options to require:
"pr_calllightning":{levels:[55,58,61,64],type:2,require:[['pr_eternalservent_light','pr_eternalservent_dark'],'pr_suffermemory'],shards:[275,290,732,768],abyss:[76350,91800,187950,205450]},
You can immediately see the advantage of a scalable database, the necessary change was made without any problems and wasted time. The skill suffix was later also used for the stigma filter for a particular race.
The tree is counted each time a class is changed and in the document is displayed in a horizontal orientation based on tables. With blocks it would be too hard to position in my opinion.

Almost final view of the interface
Insertion of improved stigm is carried out directly from the icons in the tree along with all the leaves necessary for them. What is undoubtedly convenient than setting stigm in order. If there are not enough free slots, the icon is dimmed, the already established stigmas are also marked on the tree. The visualization should be as understandable as possible without prompts: red is not possible, green is already there, gray is inaccessible. If necessary, add text hints.
Tips and descriptions
With descriptions of skills things are more complicated than it seems at first glance. Each skill is a description of its main properties (cost, time of use and reuse, goals, etc.) and from 1 to 4 effects that describe its action. And if the main description is clear, the effects are described by a set of nameless fields reserved1-reserved24 meaning and a set that changes for each type of effect. It took more time to analyze these values ​​of time than to write the entire calculator, but for all the skills of the game characters, the necessary values ​​were compared with their logic as a result.
The description line is a template for the substitution of this type:
, [%e1.SpellATK_Instant.FixDamage] . . : [%First_Target_Valid_Distance] . [%e2.SpellATK.Damage] . [%e2.SpellATK.CheckTime] : [%e2.SpellATK.RemainTime] [%e4.StatDown.StatName] -[%e4.StatDown.Value]. , .
The value pattern will be in general: the effect number, its name, and the name of the attribute. The basic properties of a skill are recorded without effect prefixes. It is better to leave the line in its original form, any alteration of it will complicate the database update. So the substitution of values ​​in the description and, accordingly, the base should be adjusted to the kind that is in the original, unless the labor costs for it exceed the conversion of the source code into a more convenient form for work.
The correspondence of the attributes has already been compiled and it is possible to make without any problems the converter of the description of skills into the necessary JSON format similar to the skills themselves, except that instead of division into classes, now the division into levels. For the above skill, the data looks like this:
"pr_painlinks":{'1':{'e1.spellatk_instant.fixdamage':'532','first_target_valid_distance':'25','e2.spellatk.checktime':'12','cost':'317 MP','cooltime':'300','e4.statdown.statname':'MagicalResist','e2.spellatk.remaintime':'120','e4.statdown.value':'400','casttime':'2','e2.spellatk.damage':'532'}, '2':{'e1.spellatk_instant.fixdamage':'557','cost':'333 MP','e2.spellatk.damage':'557'}, '3':{'e1.spellatk_instant.fixdamage':'590','cost':'355 MP','e2.spellatk.damage':'590'}, '4':{'e1.spellatk_instant.fixdamage':'642','cost':'386 MP','e2.spellatk.damage':'642'}, '5':{'e1.spellatk_instant.fixdamage':'690','cost':'413 MP','e2.spellatk.damage':'690'}, '6':{'e1.spellatk_instant.fixdamage':'738','cost':'442 MP','e2.spellatk.damage':'738'}, '7':{'e1.spellatk_instant.fixdamage':'799','cost':'508 MP','e2.spellatk.damage':'824'}},
Given that most values ​​are repeated for all skill levels, it makes no sense to specify them each time. So for the 1st level all properties are written, and then only changing. When filling the template, moving from the desired level down through all the previous ones, we get complete information about the properties. I strongly recommend to bring all identifiers to a single register if the source data is not sensitive to it - eliminates unnecessary checks and random errors.
It is easiest to substitute values ​​with a regular expression with a callback:
desc.replace(/\[%(.*?)\]%?/g, function(){ ... ... });
If the property stores for some time - it is converted into a human-readable line (for example, instead of "90" it is written "1 min. 30 sec."), If the name of the character characteristic or some element of game mechanics (for example, "stunning") - then it is necessary to substitute its name in accordance with the selected language. The remaining numbers remain as is.
In conclusion, two consecutive percentages must be removed from the resulting line (the percentage can be both in the attribute value and after the pattern in the description line, the translators are people too and cannot know such things where to put the percentage and not) or points ( -for abbreviations "sec." and so on), which could happen with the substitution.
The time of use, reuse and cost of the skill are not indicated in the description text. They need to be added in addition.

Appearance hints
With regard to the position of the display is worth considering 2 nuances.
First, the tooltip must be shifted relative to the stigma slot so as not to overlap the icon and level tooltip.
Secondly, the tooltip may not fit on the screen to the right and it should be displayed on the opposite side of the cursor if it, of course, fits there. A similar test should be done for the bottom edge of the tooltip. This is very often neglected, and some pop-up windows on small monitors crawl off the screen.

Tip positioning
Multilingualism
It just so happened that I devoted little time to this moment and all of the hoo-yaps were done. But it is impossible not to mention this section, especially since multilingualism was stated in the requirements.
The easiest option is to abandon the dynamic change of the language and make it a full page reload. There are 2 separate js files with strings in the appropriate language and the desired one is connected to the page. This happens transparently to the user because when switching the hash is saved in the link and when the page is reloaded, the calculator status is exactly the same as it was before the language was changed.
When you first enter a page, the language is determined by the Accept-Language header from the browser, then stored in Cookies and use the value from there. In the way of cookies it is better to put the domain root and thereby save the language settings for the entire site. This is convenient for the user and will save him from unnecessary manipulations with an incorrectly defined Accept-Language.
For dynamic content, an auxiliary function is used that takes the desired value from the language js file, with the simplest template-ability.
function getLang(category, name) { if (lang[category] && lang[category][name]) { var result = lang[category][name]; for (var i = 1; i < arguments.length -1; ++i) { var re = new RegExp('%'+i, 'g'); result = result.replace(re, arguments[i+1]); } return result; } return name; } ... lang = $.extend(lang, { base : { ... 'reqlvl':' %1 ', ... getLang('base', 'reqlvl', 65)
For static content, localized strings are substituted when the page is loaded based on the container id. It is logical that the text should be indicated in the markup of the document, albeit in this way.
Already after I did everything in this way I found out that in HTML 5 there is such a thing as data attributes and I don’t need to write anything in id. In the correct form, my method would look like this:
<span class="iface-string" data-stringid="copyright"></span>
$(".iface-string").html(function() { return getLang('interface', $(this).data('stringid')); });
Such work with languages ​​is quite enough for a small project, but it requires a bit of php code (language definition, substitution of the necessary js).
To reduce or not to reduce?
So the main functionality is ready. It remains only to save the result. The first thing needed for this is a convenient and short identifier for classes and skills. Since the game identifiers obviously do not fall under this definition, you will have to make your own. What could be simpler and shorter than the sequence number? Yes, perhaps nothing. The only thing that needs to be done is to make sure that the numbering is not violated when updating the database.
The amount of stigma can vary, but the level, race and class is always there. So they should go at the beginning of the line with the result, followed by stigma indicating the level. For example, like this:
0,65,0,7,0,18,6,28,2,1,0,12,0,15,0,6,6,13,4,19,6,14,6,32,5, 29.3Well, such a record cannot be called short. So it is necessary to reduce the number of characters by inventing an encoding algorithm.
If you remove the delimiters between the numbers, it will be impossible to determine where one number ends and a new one begins. It would seem that the problem is solved by increasing the base of the number system, but who can guarantee that in a year the sequence number will fit into this base? Now the maximum number of stigmas in a class is 39, which already excludes the use of toString (). Need another option.
Then I decided to use another solution. Letters be used as numbers, and the capital letter means that the next number refers to the same number. those. the number ends with a lowercase letter. That is, the string “baa” corresponds to “1.0.0”, and the string “Ba” to “26.0”. Thus, there is no need for separators between numbers and almost all stigmas fit into 1 symbol. You can numbering not in order, but the most rare stigma to put a larger identifier. With this way, the string containing the class, level and 12 stigmas is obtained:
... / stigma / # aCnahasgBccbamapaggnetgogBgfBddIn my opinion, a fairly short link is obtained in the end so as not to make the implementation of the reduction. Fans of twitter let them use their services.
The link is written in location.hash every time the set is changed, it is important to avoid looping if the set is updated from the event hash. Watching the hash is highly desirable, because if the user inserts a link to another set in the same tab in the address bar, the page will not reload, the address before the hash is completely the same. Accordingly, in this case, the link will not load without an event handler, which is clearly not consistent with the expected result.
Also, for a clearer perception, the link can be duplicated in the text field somewhere in the interface, not everyone pays attention to the address bar. With any click on the field, you need to select the entire text and block the input / editing. Or you can use the Clipboard API, but not everywhere it will work without crutches (hi IE). The best option is to check the presence of the Clipboard API and display the Share button or the text field otherwise, if religion, of course, allows you to use draft standards. Personally, I am not allowed to be lazy, and even with the link field, the functionality looks somehow more obvious.
Summary
In just a couple of weeks it took a couple of hours a day to create a complete version of the calculator. The structure of the code and data allows you to easily add new classes and skills to them, quickly update the descriptions and values ​​of skills. Already on the day of the release of the update on the test server of Korea, you can have an idea about the new skills.
The ability to save links to a set of stigmas helps in compiling guides and publishing videos or showing off your character in the forum signature.
At present, on average, the calculator accounts for 4000 visits per day and 300 unique users, provided that no advertising of the resource was made because it is not needed - this is not a commercial development.

Visitor statistics for the last month
I hope the tips from the article will help someone in the development of their projects or motivate to develop something useful.
PS: I do not want to publish the link, since the hosting is small and will die. If anyone is interested, you can ask in a personal or use search engines.
PPS: There is also an equipment calculator for this topic that started it all
and a small piece “with a touch” on WebGL with game models and animation, abandoned at the “oh nifiga!” stage. works! Further boring saw
If there is time, perhaps this publication will not end.