URI:
       ## 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.
  HTML 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.
  HTML 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
  HTML https://github.com/teeworlds-utilities/teeworlds-utilities