using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Xml.Serialization;
using System.Windows.Forms;

namespace WTD_Editor
{
    public class Map
    {
        #region all global defines

        // All defines
        public const string MAP_HEADER_V3 = "TDMv3";    // header for maps version 3 (new format)
        public const int MAP_HEADER_SIZE = 5;   // map header size
        public const int MAP_FULL_HEADER_SIZE = 13;   // map full header size
        public const int NOTHING = 0;
        public const int AUTHOR_NAME_MAX = 32;  // max number of characters for author name
        public const int CONTACT_MAX = 64;      // max number of characters for contact
        public const int VERSION = 8;           // max number of characters for version
        public const int PREVIEW_WIDTH = 256;   // width of the preview image
        public const int PREVIEW_HEIGHT = 256;  // height of the preview image
        public const int TOTAL_TOWERS = 64;     // total number of towers
        public const int MAP_NAME_MAX = 24;     // max number of characters for map names
        public const int NAME_MAX = 20;         // max number of characters for names
        public const int INFO_MAX = 128;        // max number of characters for evolution info
        public const int DESC_MAX = 80;         // max number of characters for tower description
        public const int WELCOME_MAX = 96;      // max number of characters for welcome message
        public const int PRESENTATION_MAX = 256; // max number of characters for presentation message
        public const int COMMENTS_MAX = 64;     // max number of characters for comments
        public const int SPECIAL_MAX = 4;       // max number of special effects for towers
        public const int SP_PARAMS = 3;         // number of parameters for special effects
        public const int EVO_TOWERS = 11;       // number of towers for an evolution
        public const int IMMUNE_MAX = 4;        // max number of immune for a monster
        public const int IM_PARAMS = 3;         // number of parameters for immunes
        public const int ROUNDS_MAX = 256;      // max number of rounds
        public const int TOWERS_MAX = 256;      // max number of towers
        public const int EVOLUTIONS_MAX = 8;    // max number of evolutions
        public const int MONSTERS_MAX = 256;    // max number of monsters
        public const int CLANS_MAX = 8;         // max number of clans
        public const int UPGRADES_MAX = 8;      // max number of upgrades per tower
        public const int MAP_WIDTH_MAX = 128;   // max horizontal tiles for a map
        public const int MAP_HEIGHT_MAX = 128;  // max vertical tiles for a map
        public const int MAX_WAYPOINTS = 256/2;   // max number of waypoints
        public const int MAX_PATHS = 4;           // max number of paths
        public const int MONSTERS_SPAWN_MAX = 256;    // max number of monsters to be spawned

        public const int LINES_WELCOME = 3;     // max lines for welcome message
        public const int LINES_PRESENTATION = 7; // max lines for presentation text
        public const int LINES_COMMENT = 3;     // max lines for round comment
        public const int LINES_DESCRIPTION = 6; // max lines for tower description
        public const int LINES_INFO = 7;        // max lines for clan / evolution info


        // Special effects (effects parameters) 
        public enum SP 
        {
            ADD_MAX_TOWERS = 1,
            SLOW = 2,           // slow down (effect time in msec, move delay in vbl, effect delay in vbl)
            POISON = 3,         // poison, damage every sec (effect time in msec, min damage, max damage)
            CRITIC = 4,         // critic damage (percentage to do, 0, 0)
            PIERCE = 5,         // ignore armor (armor malus, 0, 0)
            FIRE = 6,           // fire magic (min damage, max damage, 0)
            WATER = 7,          // water magic (min damage, max damage, 0)
            LIGHTNING = 8,      // lightning magic (min damage, max damage, 0)
            WIND = 9,           // wind magic (min damage, max damage, 0)
            SPLASH = 10,        // splash damage (radius, 0, 0) ONLY EFFECTIVE WITH GROUND MONSTERS
            REVEAL = 11,        // reveal invisible monsters (radius, 0, 0)
        }

        // Immunes (immunes parameters)
        public enum IMM
        {
            SLOW = 2,           // slow (effect time malus in msec, move delay malus in vbl, effect delay bonus in vbl)
            POISON = 3,         // poison (effect time malus in msec, damage malus, 0)
            FIRE = 6,           // fire magic (damage malus, 0, 0)
            WATER = 7,          // water magic (damage malus, 0, 0)
            LIGHTNING = 8,      // lightning magic (damage malus, 0, 0)
            WIND = 9,           // wind magic (damage malus, 0, 0)
            IM_NORMAL = 12,     // immune to normal damage
            IM_PIERCE = 13,     // immune to pierce effects
            IM_MAGIC = 14,      // immune to magic [fire, water, lightning & wind] (0, 0, 0)
        }

        // Game engine defines
        public const int MONSTERS_INST_MAX = 40;    // Maximum monsters instances
        public const int TOWERS_INST_MAX = 40;      // Maximum tower instances
        public const int ATTACKS_INST_MAX = 40;     // Maximum attack instances

        // Palettes IDs (screen 0)
        public enum M_PAL 
        {
            RED = 8,
            BLUE = 9,
            GREEN = 10,
            BLACK = 11
        }

        public enum T_PAL
        {
            NORMAL = 1,
            FROZEN = 2,
            POISON = 3,
//            DARK = 4
        }

        // Minimap size
        public const int MINIMAP_WIDTH = 58;   // width of the minimap
        public const int MINIMAP_HEIGHT = 62;  // height of the minimap

        // Spawn rate for monsters
        public enum SR
        {
            SLOW = 119,
            NORMAL = 59,
            FAST = 29,
            VERYFAST = 15,
            CONTINUOUS = 9,
            EVERY_5_SEC = 299,
            EVERY_10_SEC = 599,
            EVERY_15_SEC = 899,
            EVERY_20_SEC = 1199,
        }

        // Walk speed for monsters
        public enum W_SPEED
        {
            NORMAL = 1,
            FAST = 2,
            VERYFAST = 4
        }

        // Animation speed for monsters
        public enum MA_SPEED
        {
            SLOW = 8,
            NORMAL = 4,
            FAST = 2,
            VERYFAST = 1
        }

        // Projectiles speed for towers
        public enum A_SPEED
        {
            VERYSLOW = 4,
            SLOW = 8,
            NORMAL = 16,
            FAST = 32,
            VERYFAST = 64,
        }

        // Multiple paths spawn method
        public enum MPS_METHOD
        {
            ALTERNATE,
            SIMULATENOUS,
            ORDERED,
        }

        // Tilesets
        public static TileSet[] TILESETS =
        {
            new TileSet(WTD_Editor.Properties.Resources.tileset_summer_grass, "GRASS_A"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_summer_grass_b, "GRASS_B"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_ice_valley, "ICE_A"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_ice_valley_b, "ICE_B"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_ruins, "RUINS_A"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_ruins_b, "RUINS_B"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_poison, "POISON_A"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_poison_b, "POISON_B"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_dungeon, "DUNGEON_A"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_dungeon_b, "DUNGEON_B"),
            new TileSet(WTD_Editor.Properties.Resources.tileset_orange_b, "ORANGE"),
        };

        // Monsters
        public const int NUM_MONSTERS = 36;
        public static string[] MONSTERS_NAMES = {
            "PEON",
            "AXETHROWER",
            "GRUNT",
            "PEASANT",
            "ROGUE",
            "SHEEP",
            "DAEMON",
            "DEATH_KNIGHT",
            "GOBLINS",
            "DWARVES",
            "FOOTMAN",
            "KNIGHT",
            "MAGE",
            "OGRE",
            "BIG_SHEEP",
            "SKELETON",
            "GRYPHON",
            "HELICOPTER",
            "ZEPPELIN",
            "DRAGON",
            "BALLISTA",
            "BATTLESHIP",
            "BUFFALO",
            "CATAPULT",
            "CLERIC",
            "ELVEN_DESTROYER",
            "ELVEN_TANKER",
            "HUMAN_TANKER",
            "HUMAN_TRANSPORT",
            "JUGGERNAUT",
            "NECROLYTE",
            "ORC_TRANSPORT",
            "SEAL",
            "SUBMARINE",
            "TROLL_DESTROYER",
            "TURTLE"
        };

        public static Bitmap[] MONSTERS_GFX = {
            WTD_Editor.Properties.Resources.m_peon,
            WTD_Editor.Properties.Resources.m_axethrower,
            WTD_Editor.Properties.Resources.m_grunt,
            WTD_Editor.Properties.Resources.m_peasant,
            WTD_Editor.Properties.Resources.m_archer,
            WTD_Editor.Properties.Resources.m_sheep,
            WTD_Editor.Properties.Resources.m_daemon,
            WTD_Editor.Properties.Resources.m_death_knight,
            WTD_Editor.Properties.Resources.m_goblins,
            WTD_Editor.Properties.Resources.m_dwarves,
            WTD_Editor.Properties.Resources.m_footman,
            WTD_Editor.Properties.Resources.m_knight,
            WTD_Editor.Properties.Resources.m_mage,
            WTD_Editor.Properties.Resources.m_ogre,
            WTD_Editor.Properties.Resources.m_sheep_big,
            WTD_Editor.Properties.Resources.m_skeleton,
            WTD_Editor.Properties.Resources.m_gryphon,
            WTD_Editor.Properties.Resources.m_helicopter,
            WTD_Editor.Properties.Resources.m_zeppelin,
            WTD_Editor.Properties.Resources.m_dragon,
            WTD_Editor.Properties.Resources.m_ballista,
            WTD_Editor.Properties.Resources.m_battleship,
            WTD_Editor.Properties.Resources.m_buffalo,
            WTD_Editor.Properties.Resources.m_catapult,
            WTD_Editor.Properties.Resources.m_cleric,
            WTD_Editor.Properties.Resources.m_elven_destroyer,
            WTD_Editor.Properties.Resources.m_elven_tanker,
            WTD_Editor.Properties.Resources.m_human_tanker,
            WTD_Editor.Properties.Resources.m_human_transport,
            WTD_Editor.Properties.Resources.m_juggernaut,
            WTD_Editor.Properties.Resources.m_necrolyte,
            WTD_Editor.Properties.Resources.m_orc_transport,
            WTD_Editor.Properties.Resources.m_seal,
            WTD_Editor.Properties.Resources.m_submarine,
            WTD_Editor.Properties.Resources.m_troll_destroyer,
            WTD_Editor.Properties.Resources.m_turtle,
        };

        // Attacks
        public const int ATTACKS_NUM = 46;  // number of different attacks
        public enum ATTACKS
        {
            ARROW,
            BULLET,
            CANNON,
            FIRE_LIGHTNING,
            FIREROCK,
            ICEROCK,
            LIGHTNING,
            MUDROCK,
            POISON_LIGHTNING,
            POISONROCK,
            BLASTER_SHOT,
            BLUE_BEAM,
            BLUE_BLAST,
            BLUE_GRENADE,
            BLUE_PSY,
            FIRE_BLASTER_SHOT,
            FIRE_BREATH,
            FIRE_SPORE,
            FIRE_VIRUS,
            GREENFLAME_CANNON,
            GREEN_BEAM,
            GREEN_BLAST,
            GREEN_PSY,
            GRENADE,
            ICEFLAME_CANNON,
            ICE_BLASTER_SHOT,
            ICE_BREATH,
            ICE_SPORE,
            ICE_VIRUS,
            MAGENTA_BLAST,
            POISON_BLASTER_SHOT,
            POISON_BREATH,
            POISON_SPORE,
            POISON_VIRUS,
            REDFLAME_CANNON,
            RED_BEAM,
            RED_BLAST,
            RED_PSY,
            ROCKET,
            STATIC_BLUE,
            STATIC_GREEN,
            STATIC_RED,
            TORNADO,
            YELLOW_BEAM,
            YELLOW_GRENADE,
            YELLOW_PSY,
        }

        public static Bitmap[] ATTACKS_GFX = {
            WTD_Editor.Properties.Resources.s_arrow,
            WTD_Editor.Properties.Resources.s_bullet,
            WTD_Editor.Properties.Resources.s_cannon,
            WTD_Editor.Properties.Resources.s_fire_lightning,
            WTD_Editor.Properties.Resources.s_firerock,
            WTD_Editor.Properties.Resources.s_icerock,
            WTD_Editor.Properties.Resources.s_lightning,
            WTD_Editor.Properties.Resources.s_mudrock,
            WTD_Editor.Properties.Resources.s_poison_lightning,
            WTD_Editor.Properties.Resources.s_poisonrock,
            WTD_Editor.Properties.Resources.s_blaster_shot,
            WTD_Editor.Properties.Resources.s_blue_beam,
            WTD_Editor.Properties.Resources.s_blue_blast,
            WTD_Editor.Properties.Resources.s_blue_grenade,
            WTD_Editor.Properties.Resources.s_blue_psy,
            WTD_Editor.Properties.Resources.s_fire_blaster_shot,
            WTD_Editor.Properties.Resources.s_fire_breath,
            WTD_Editor.Properties.Resources.s_fire_spore,
            WTD_Editor.Properties.Resources.s_fire_virus,
            WTD_Editor.Properties.Resources.s_greenflame_cannon,
            WTD_Editor.Properties.Resources.s_green_beam,
            WTD_Editor.Properties.Resources.s_green_blast,
            WTD_Editor.Properties.Resources.s_green_psy,
            WTD_Editor.Properties.Resources.s_grenade,
            WTD_Editor.Properties.Resources.s_iceflame_cannon,
            WTD_Editor.Properties.Resources.s_ice_blaster_shot,
            WTD_Editor.Properties.Resources.s_ice_breath,
            WTD_Editor.Properties.Resources.s_ice_spore,
            WTD_Editor.Properties.Resources.s_ice_virus,
            WTD_Editor.Properties.Resources.s_magenta_blast,
            WTD_Editor.Properties.Resources.s_poison_blaster_shot,
            WTD_Editor.Properties.Resources.s_poison_breath,
            WTD_Editor.Properties.Resources.s_poison_spore,
            WTD_Editor.Properties.Resources.s_poison_virus,
            WTD_Editor.Properties.Resources.s_redflame_cannon,
            WTD_Editor.Properties.Resources.s_red_beam,
            WTD_Editor.Properties.Resources.s_red_blast,
            WTD_Editor.Properties.Resources.s_red_psy,
            WTD_Editor.Properties.Resources.s_rocket,
            WTD_Editor.Properties.Resources.s_static_blue,
            WTD_Editor.Properties.Resources.s_static_green,
            WTD_Editor.Properties.Resources.s_static_red,
            WTD_Editor.Properties.Resources.s_tornado,
            WTD_Editor.Properties.Resources.s_yellow_beam,
            WTD_Editor.Properties.Resources.s_yellow_grenade,
            WTD_Editor.Properties.Resources.s_yellow_psy,    
        };

        public enum A_RANGE
        {
            CLOSE_UP = 32,
            SHORT = 48,
            AVERAGE = 64,
            LONG = 80,
            VERY_LONG = 96,
            SUPER_LONG = 128,
        }
        public enum A_RELOAD
        {
            SUPER_SLOW = 100,
            VERY_SLOW = 75,
            SLOW = 50,
            STANDARD = 25,
            FAST = 15,
            VERY_FAST = 5,
        }
        
        // Attacks sounds
        public const int NUM_SOUNDS = 2;
        public static string[] SOUNDS_NAMES =
        {
            "ANCESTRAL GUARDIAN MISSILE LAUNCH",
            "ARROW ATTACK",
            "BANSHEE MISSILE LAUNCH",
            "BOAT ATTACK",
            "BRISTLE BACK MISSILE LAUNCH",
            "CANNON MISSILE LAUNCH",
            "DESTROYER MISSILE",
            "FARSEER MISSILE LAUNCH",
            "FERAL SPIRIT TARGET",
            "FIREBALL MISSILE LAUNCH",
            "FLAK CANNON",
            "FRAG BOMBS",
            "GRYPHON RIDER MISSILE LAUNCH",
            "GUARD TOWER MISSILE LAUNCH",
            "HERO DEMON MISSILE LAUNCH",
            "IMPALE LAUNCH",
            "NECROMANCER MISSILE LAUNCH",
            "PHASE SHIFT",
            "PHOENIX ATTACK",
            "SEARING ARROW MISSILE LAUNCH",
            "SORCERESS CAST ATTACK",
            "SORCERESS MISSILE LAUNCH",
            "STORMBOLT LAUNCH",
            "TROLL BATRIDER MISSILE",
            "ZIGGURAT MISSILE LAUNCH",
        };

        public static System.IO.UnmanagedMemoryStream[] SOUNDS_RES = 
        {
            WTD_Editor.Properties.Resources.AncestralGuardianMissileLaunch,
            WTD_Editor.Properties.Resources.ArrowAttack,
            WTD_Editor.Properties.Resources.BansheeMissileLaunch,
            WTD_Editor.Properties.Resources.BoatAttack,
            WTD_Editor.Properties.Resources.BristleBackMissileLaunch,
            WTD_Editor.Properties.Resources.CannonMissileLaunch,
            WTD_Editor.Properties.Resources.DestroyerMissile,
            WTD_Editor.Properties.Resources.FarseerMissileLaunch,
            WTD_Editor.Properties.Resources.FeralSpiritTarget,
            WTD_Editor.Properties.Resources.FireBallMissileLaunch,
            WTD_Editor.Properties.Resources.FlakCannon,
            WTD_Editor.Properties.Resources.FragBombs,
            WTD_Editor.Properties.Resources.GryphonRiderMissileLaunch,
            WTD_Editor.Properties.Resources.GuardTowerMissileLaunch,
            WTD_Editor.Properties.Resources.HeroDemonMissileLaunch,
            WTD_Editor.Properties.Resources.ImpaleLaunch,
            WTD_Editor.Properties.Resources.NecromancerMissileLaunch,
            WTD_Editor.Properties.Resources.PhaseShift,
            WTD_Editor.Properties.Resources.PhoenixAttack,
            WTD_Editor.Properties.Resources.SearingArrowMissileLaunch,
            WTD_Editor.Properties.Resources.SorceressCastAttack,
            WTD_Editor.Properties.Resources.SorceressMissileLaunch,
            WTD_Editor.Properties.Resources.StormBoltLaunch,
            WTD_Editor.Properties.Resources.TrollBatriderMissile,
            WTD_Editor.Properties.Resources.ZigguratMissileLaunch
        };

        #endregion

        #region attributes

        // attributes
        public string version;
        public string author;
        public string contact;
        public string map_version;
        public string name;
        public string welcome;
        public bool simple_presentation;
        public string presentation;
        public string map_Pal;
        public string map_Tiles;
        public string map_Map;
        public int map_Width;
        public int map_Height;
        public string minimap_Sprite;
        public string minimap_Pal;
        public int minimap_x_adjust;
        public int minimap_y_adjust;
        public int start_x;
        public int start_y;
        public int spawn_x;
        public int spawn_y;
        public int gold_start;
        public int sell_pct;
        public int max_towers_start;
        public int init_delay;
        public int lifes;
        public bool timed_gold;
        public int initial_bonus;
        public List<Waypoint> waypoints;
        public List<Round> rounds;
        public List<Tower> towers;
        public List<Clan> clans;
        public List<Evolution> evolutions;
        public List<Monster> monsters;
        public TileMap tilemap;
        public bool w3_damage_style;
        public int minimap_colors;
        public int num_paths;
        public List<List<Waypoint>> paths;
        public bool use_fast_pathfinder;

        [XmlIgnore]
        public Bitmap minimap;
        [XmlIgnore]
        public Bitmap gfxmap;

        #endregion

        public Map()
        {
            author = "";
            contact = "";
            map_version = "1.0";
            name = "another TD map";
            welcome = "";
            simple_presentation = false;
            presentation = "";
            map_Pal = "";
            map_Tiles = "";
            map_Map = "";
            map_Width = 0;
            map_Height = 0;
            minimap_Sprite = "";
            minimap_Pal = "";
            minimap_x_adjust = 0;
            minimap_y_adjust = 0;
            start_x = 0;
            start_y = 0;
            spawn_x = 0;
            spawn_y = 0;
            gold_start = 0;
            sell_pct = 0;
            max_towers_start = 0;
            init_delay = 0;
            lifes = 0;
            timed_gold = false;
            initial_bonus = 0;
            waypoints =  new List<Waypoint>();
            rounds = new List<Round>();
            towers = new List<Tower>();
            clans = new List<Clan>();
            evolutions = new List<Evolution>();
            monsters = new List<Monster>();
            tilemap = null;
            w3_damage_style = true;
            minimap_colors = 0;
            num_paths = 1;
            paths = new List<List<Waypoint>>();
            use_fast_pathfinder = false;
        }

        private void addString(List<Byte> output, string str, int max)
        {
            for (int i = 0; i < max; i++)
            {
                if (i < str.Length)
                    output.Add((Byte)str[i]);
                else
                    output.Add((Byte)'\0');
            }
        }

        public void addFillBytes(List<Byte> output, int num)
        {
            for (int i = 0; i < num; i++)
                output.Add(new Byte());
        }

        public void fillIndexes()
        {
            foreach (Clan c in clans)
            {
                c.evolutions_idx.Clear();
                foreach (Evolution e in c.evolutions) 
                    c.evolutions_idx.Add(evolutions.IndexOf(e));
            }

            foreach (Evolution e in evolutions)
            {
                e.towers_idx.Clear();
                foreach (Tower t in e.towers)
                    e.towers_idx.Add(towers.IndexOf(t));
            }

            foreach (Round r in rounds)
            {
                r.type_idx = monsters.IndexOf(r.type);
            }

            foreach (Tower t in towers)
            {
                t.upgrades_idx.Clear();
                foreach (Tower u in t.upgrades)
                    t.upgrades_idx.Add(towers.IndexOf(u));
            }
        }

        public List<Byte> buildMap()
        {
            List<Byte> output = new List<byte>();
            Random rnd = new Random();            

            // PAGfx conversion
            PAGfx_Convertor convert = new PAGfx_Convertor(this);
            convert.CreateMinimapSprite();
            convert.CreateMapBg();
            convert.WriteMinimapPalette();
            convert.WriteMapPalette();


            /*=============================*/
            /*** MAP HEADER (SINCE V0.5) ***/
            /*=============================*/

//            addString(output, MAP_HEADER_V3, MAP_HEADER_SIZE);
//            output.AddRange(BitConverter.GetBytes((UInt32)rnd.Next())); // random number for id instead of size, hash will be added at the end

            /***************************************************************/
            
            // Map name
            addString(output, name, MAP_NAME_MAX);

            // Map presentation
            addString(output, presentation, PRESENTATION_MAX);

            // Map simple presentation
            output.AddRange(BitConverter.GetBytes(simple_presentation));
            addFillBytes(output, 3);    // word align

            // Minimap palette
            output.AddRange(BitConverter.GetBytes((UInt32)convert.minimap_pal_bytes.Length));
            output.AddRange(convert.minimap_pal_bytes.ToArray());
//            addFillBytes(output, (int)(4 - (convert.minimap_pal_bytes.Length % 4))%4);    // word align

            // Minimap sprite
            output.AddRange(BitConverter.GetBytes((UInt32)convert.minimap_bytes.Length));
            output.AddRange(convert.minimap_bytes.ToArray());
//            addFillBytes(output, (int)(4-(convert.minimap_bytes.Length % 4))%4);    // word align
            
            // Map info
            output.AddRange(BitConverter.GetBytes((UInt32)4));
            output.AddRange(BitConverter.GetBytes((UInt32)map_Width*16));
            output.AddRange(BitConverter.GetBytes((UInt32)map_Height*16));

            // Map palette
            output.AddRange(BitConverter.GetBytes((UInt32)convert.map_pal_bytes.Length));
            output.AddRange(convert.map_pal_bytes.ToArray());
//            addFillBytes(output, (int)(4-(convert.map_pal_bytes.Length % 4))%4);    // word align

            // Map tilemap
            output.AddRange(BitConverter.GetBytes((UInt32)convert.map_bytes.Length));
            output.AddRange(convert.map_bytes.ToArray());
//            addFillBytes(output, (int)(4-(convert.map_bytes.Length % 4))%4);    // word align

            // Map tiles
            output.AddRange(BitConverter.GetBytes((UInt32)convert.tiles_bytes.Length));
            output.AddRange(convert.tiles_bytes.ToArray());
//            addFillBytes(output, (int)(4-(convert.tiles_bytes.Length % 4))%4);    // word align

            // Welcome message
            addString(output, welcome, WELCOME_MAX);

            // Minimap adjustments
            output.AddRange(BitConverter.GetBytes((UInt32)minimap_x_adjust));
            output.AddRange(BitConverter.GetBytes((UInt32)minimap_y_adjust));

            // Starting position
            output.AddRange(BitConverter.GetBytes((Int32)start_x));
            output.AddRange(BitConverter.GetBytes((Int32)start_y));

            // Spawn position
            output.AddRange(BitConverter.GetBytes((Int32)spawn_x));
            output.AddRange(BitConverter.GetBytes((Int32)spawn_y));

            // Various variables
            output.AddRange(BitConverter.GetBytes((UInt32)gold_start));
            output.AddRange(BitConverter.GetBytes((UInt32)sell_pct));
            output.AddRange(BitConverter.GetBytes((UInt32)max_towers_start));
            output.AddRange(BitConverter.GetBytes((UInt32)init_delay));
            output.AddRange(BitConverter.GetBytes((UInt32)lifes));
            output.AddRange(BitConverter.GetBytes(timed_gold));
            addFillBytes(output, 3);    // word align
            output.AddRange(BitConverter.GetBytes((UInt32)initial_bonus));

            /***************************************************************/

            // collision map - monsters
            for (int j = 0; j < map_Height; j++)
                for (int i = 0; i < map_Width; i++)
                    output.AddRange(BitConverter.GetBytes((UInt32)(tilemap.monster_map[i][j]*2)));

            // collision map - towers
            for (int j = 0; j < map_Height; j++)
                for (int i = 0; i < map_Width; i++)
                    output.AddRange(BitConverter.GetBytes((UInt32)tilemap.tower_map[i][j]));

            /***************************************************************/

            // waypoints
            output.AddRange(BitConverter.GetBytes((UInt32)waypoints.Count));

            foreach (Waypoint w in waypoints)
            {
                output.AddRange(BitConverter.GetBytes((UInt32)w.x));
                output.AddRange(BitConverter.GetBytes((UInt32)w.y));
            }

            /***************************************************************/

            // rounds
            output.AddRange(BitConverter.GetBytes((UInt32)rounds.Count));

            foreach (Round r in rounds)
            {
                addString(output, r.comment, COMMENTS_MAX);
                output.AddRange(BitConverter.GetBytes((UInt32)r.number));
                output.AddRange(BitConverter.GetBytes((UInt32)monsters.IndexOf(r.type)));
                output.AddRange(BitConverter.GetBytes((UInt32)r.spawn_rate));
                output.AddRange(BitConverter.GetBytes((UInt32)r.nextround_delay));
                output.AddRange(BitConverter.GetBytes((UInt32)r.gold_bonus));
            }

            /***************************************************************/

            // towers
            output.AddRange(BitConverter.GetBytes((UInt32)towers.Count));

            foreach (Tower t in towers)
            {
                addString(output, t.name, NAME_MAX);
                addString(output, t.desc, DESC_MAX);
                output.AddRange(BitConverter.GetBytes((UInt32)t.pal));
                output.AddRange(BitConverter.GetBytes((UInt32)t.gfx_idx));
                output.AddRange(BitConverter.GetBytes((UInt32)t.icon_idx));
                output.AddRange(BitConverter.GetBytes((UInt32)t.attack_type));
                output.AddRange(BitConverter.GetBytes((UInt32)t.sound));
                output.AddRange(BitConverter.GetBytes((UInt32)t.price));
                output.AddRange(BitConverter.GetBytes((UInt32)t.min_damage));
                output.AddRange(BitConverter.GetBytes((UInt32)t.max_damage));
                output.AddRange(BitConverter.GetBytes((UInt32)t.speed));
                output.AddRange(BitConverter.GetBytes((UInt32)t.reload));
                output.AddRange(BitConverter.GetBytes((UInt32)t.range));

                output.AddRange(BitConverter.GetBytes(t.air));
                addFillBytes(output, 3);    // word align

                output.AddRange(BitConverter.GetBytes(t.ground));
                addFillBytes(output, 3);    // word align

                output.AddRange(BitConverter.GetBytes(t.dark));
                addFillBytes(output, 3);    // word align

                output.AddRange(BitConverter.GetBytes((UInt32)t.specials.Count));
                foreach (Tower.Special sp in t.specials)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)sp.type));
                    output.AddRange(BitConverter.GetBytes((Int32)sp.param1));
                    output.AddRange(BitConverter.GetBytes((Int32)sp.param2));
                    output.AddRange(BitConverter.GetBytes((Int32)sp.param3));
                }

                output.AddRange(BitConverter.GetBytes((UInt32)t.upgrades.Count));
                foreach (Tower upg in t.upgrades)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)towers.IndexOf(upg)));
                }

                output.AddRange(BitConverter.GetBytes((UInt32)t.evo_min));
            }

            /***************************************************************/

            // evolutions
            output.AddRange(BitConverter.GetBytes((UInt32)evolutions.Count));

            foreach (Evolution e in evolutions)
            {
                addString(output, e.name, NAME_MAX);
                addString(output, e.info, INFO_MAX);
                output.AddRange(BitConverter.GetBytes((UInt32)e.icon_idx));
                output.AddRange(BitConverter.GetBytes((UInt32)e.build_icon_idx));
                output.AddRange(BitConverter.GetBytes((UInt32)e.price));
                output.AddRange(BitConverter.GetBytes((UInt32)e.min_round));

                output.AddRange(BitConverter.GetBytes((UInt32)e.towers.Count));
                foreach (Tower t in e.towers)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)towers.IndexOf(t)));
                }

                output.AddRange(BitConverter.GetBytes((UInt32)e.max_towers));
            }

            /***************************************************************/

            // clans
            output.AddRange(BitConverter.GetBytes((UInt32)clans.Count));

            foreach (Clan c in clans)
            {
                addString(output, c.name, NAME_MAX);
                addString(output, c.info, INFO_MAX);
                output.AddRange(BitConverter.GetBytes((UInt32)c.icon_idx));
                output.AddRange(BitConverter.GetBytes((UInt32)c.evolutions.Count));

                foreach (Evolution e in c.evolutions)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)evolutions.IndexOf(e)));
                }
            }

            /***************************************************************/

            // monsters
            output.AddRange(BitConverter.GetBytes((UInt32)monsters.Count));

            foreach (Monster m in monsters)
            {
                addString(output, m.name, NAME_MAX);
                output.AddRange(BitConverter.GetBytes((UInt32)m.type));
                output.AddRange(BitConverter.GetBytes((UInt32)m.pal));

                output.AddRange(BitConverter.GetBytes(m.invisible));
                addFillBytes(output, 3);    // word align

                output.AddRange(BitConverter.GetBytes((UInt32)m.gold_bonus));
                output.AddRange(BitConverter.GetBytes((UInt32)m.life));
                output.AddRange(BitConverter.GetBytes((UInt32)m.armor));
                output.AddRange(BitConverter.GetBytes((UInt32)m.speed));
                output.AddRange(BitConverter.GetBytes((UInt32)m.immunes.Count));

                foreach (Monster.Immune imm in m.immunes)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)imm.type));
                    output.AddRange(BitConverter.GetBytes((Int32)imm.param1));
                    output.AddRange(BitConverter.GetBytes((Int32)imm.param2));
                    output.AddRange(BitConverter.GetBytes((Int32)imm.param3));
                }
                
                output.AddRange(BitConverter.GetBytes((UInt32)m.anim_speed));
            }

            /***************************************************************/

            /*=============================*/
            /*** NEW FEATURES SINCE V0.4 ***/
            /*=============================*/
            
            // additional map properties
            output.AddRange(BitConverter.GetBytes(w3_damage_style));
            addFillBytes(output, 3);    // word align
            output.AddRange(BitConverter.GetBytes(use_fast_pathfinder));
            addFillBytes(output, 3);    // word align
            output.AddRange(BitConverter.GetBytes((UInt32)minimap_colors));
            output.AddRange(BitConverter.GetBytes((UInt32)num_paths));
            
            // additional monsters properties
            foreach (Monster m in monsters)
            {
                output.AddRange(BitConverter.GetBytes(m.ghost));
                addFillBytes(output, 3);    // word align
                output.AddRange(BitConverter.GetBytes((UInt32)m.size));
            }

            // additional paths
            foreach (List<Waypoint> p in paths)
            {
                output.AddRange(BitConverter.GetBytes((UInt32)p.Count));

                foreach (Waypoint w in p)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)w.x));
                    output.AddRange(BitConverter.GetBytes((UInt32)w.y));
                }
            }

            // additional rounds properties
            foreach (Round r in rounds)
            {
                output.AddRange(BitConverter.GetBytes((UInt32)r.mps_method));

                for (int i = 0; i < num_paths - 1; i++)
                {
                    output.AddRange(BitConverter.GetBytes((UInt32)r.paths_number[i]));
                    output.AddRange(BitConverter.GetBytes((UInt32)r.paths_spawn_rate[i]));
                }
            }
            
            // add map size + hash
//            output.InsertRange(MAP_FULL_HEADER_SIZE - 4, BitConverter.GetBytes((Int32)output.GetHashCode()));
            int size = output.Count;
            output.InsertRange(/*MAP_FULL_HEADER_SIZE*/ + Map.MAP_NAME_MAX, BitConverter.GetBytes((Int32)size));

            return output;
        }

    }
}
