## title: Teeworlds utilities
       ## date: "2023-07-26"
       
       This idea came to me when I was looking for a Teeworlds skin
       renderer.
       
       The ones that existed didn't suit me, as they didn't really
       respect the in-game rendering. Either the feet were too far
       out or the colors were wrong.
       
       So I decided to make my own toolbox to manipulate Teeworlds
       assets, which we use on teedata.net and for the Teedata
       Discord bot.
 (HTM) teedata.net
       
       Indirectly, other people use it, for example, to render
       skins in a Discord channel that displays messages in real
       time (fokkonaut's Discord server) or in other projects like
       TeeAssembler 2.0 that used some part of the Teeworlds
       utilities code.
 (HTM) TeeAssembler 2.0
       
         /fokkonaut_bridge.png
 (IMG) /fokkonaut_bridge.png
       
       ## Use case examples
       ### Teeworlds skin rendering
       
       Render a Teeworlds 4K skin with default and custom colors.
       
       import {
         Skin,
         ColorCode,
         ColorRGB
       } from 'teeworlds-utilities';
       
       const renderTest = async () => {
         const skin = new Skin();
       
         await
       skin.load('https://api.skins.tw/database/skins/96AATxN3DEzcGww4QhmduFCsPzaxhZO7Tq6Lh9OI.png');
       
         skin
           .render()
           .saveRenderAs('default.png', true)
           .colorTee(
             new ColorCode(6619008),
             new ColorRGB(136, 113, 255),
           )
           .render()
           .saveRenderAs('color.png', true);
       }
       
       try {
         renderTest();
       } catch (err) {
         console.error(err);
       }
       
       ### Result (4K)
       
       /render_default.png
       /render_color.png
 (IMG) /render_default.png
 (IMG) /render_color.png
       
       ### Scene
       
       A custom scene including a rendered skin.
       
       import { Scene } from 'teeworlds-utilities';
       
       const sceneTest = async () => {
         const scene = new Scene(
           'data/scenes/schemes/example.json'
         ).preprocess();
       
         await scene.renderScene();
         scene.saveScene('scene.png')
       }
       
       sceneTest();
       
       ### Result
       
       /scene.png
 (IMG) /scene.png
       
       ### Merge asset parts
       
       Here we are going to merge specific parts from a skin
       (right) to another (left).
       Any Teeworlds asset should works.
       
       /teedata_skin.png
       /alien_skin.png
 (IMG) /teedata_skin.png
 (IMG) /alien_skin.png
       
       import {
         Skin,
         SkinPart
       } from 'teeworlds-utilities';
       
       const mergeTest = async () => {
         const teedata = new Skin();
         await
       teedata.load('https://teedata.net/databasev2/skins/teedata/teedata.png');
       
         const sunny = new Skin();
         await
       sunny.load('https://teedata.net/databasev2/skins/irradiated%20sunny/irradiated%20sunny.png');
       
         teedata
           .copyParts(
             sunny,
             SkinPart.FOOT,
             SkinPart.FOOT_SHADOW,
             SkinPart.DEFAULT_EYE,
             SkinPart.ANGRY_EYE,
             SkinPart.BLINK_EYE,
             SkinPart.CROSS_EYE,
             SkinPart.HAPPY_EYE,
             SkinPart.SCARY_EYE,
             SkinPart.HAND_SHADOW,
             SkinPart.HAND,
           )
           .setEyeAssetPart(SkinPart.ANGRY_EYE)
           .render()
           .saveAs('skin.png')
           .saveRenderAs('rendered_skin.png', true)
       }
       
       try {
         mergeTest();
       } catch (err) {
         console.error(err);
       }
       
       ### Result
       
       /render_new_skin.png
       /new_skin.png
 (IMG) /render_new_skin.png
 (IMG) /new_skin.png
       
       ### More skin configuration
       
       Here we are using the SkinFull object to render some in-game
       feature like the weapon, hands and emote.
       
       import {
         Skin,
         Gameskin,
         ColorRGB,
         Emoticon,
         SkinFull,
         GameskinPart,
         EmoticonPart
       } from 'teeworlds-utilities';
       
       const fullSkinRenderConfiguration = async () => {
         const teedataSunny = new Skin();
         await teedataSunny.load('skin.png');
       
         const napolitano = new Gameskin();
         await
       napolitano.load('https://teedata.net/databasev2/gameskins/napolitano/napolitano.png');
       
         const emoticon = new Emoticon();
         await
       emoticon.load('https://teedata.net/databasev2/emoticons/default/default.png');
       
         teedataSunny
           .colorTee(
             new ColorRGB(255, 255, 255),
             new ColorRGB(255, 255, 255),
           )
           .setOrientation(345);
       
         new SkinFull()
           .setSkin(teedataSunny)
           .setGameskin(napolitano, GameskinPart.HAMMER)
           .setEmoticon(emoticon, EmoticonPart.PART_1_2)
           .process()
           .saveAs('skin_with_weapon_and_emote.png', true);
       }
       
       try {
         fullSkinRenderConfiguration();
       } catch (err) {
         console.error(err);
       }
       
       ### Result
       
       /skin_with_weapon_and_emote.png
 (IMG) /skin_with_weapon_and_emote.png
       
       ### Other possible result
       
       /board.png
 (IMG) /board.png
       
       ## Links
       
       https://github.com/teeworlds-utilities/teeworlds-utilities
 (HTM) https://github.com/teeworlds-utilities/teeworlds-utilities