Last active
October 29, 2024 14:06
-
-
Save Hornwitser/f291638024e7e3c0271b1f3a4723e05a to your computer and use it in GitHub Desktop.
Revisions
-
Hornwitser revised this gist
May 18, 2021 . 1 changed file with 286 additions and 529 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,177 +2,167 @@ const zlib = require("zlib"); const util = require("util"); class Parser { constructor(buf) { this.pos = 0; this.buf = buf; this.last_position = { x: 0, y: 0 }; } } function read_bool(parser) { let value = read_uint8(parser) !== 0; return value; } function read_uint8(parser) { let value = parser.buf.readUInt8(parser.pos); parser.pos += 1; return value; } function read_int16(parser) { let value = parser.buf.readInt16LE(parser.pos); parser.pos += 2; return value; } function read_uint16(parser) { let value = parser.buf.readUInt16LE(parser.pos); parser.pos += 2; return value; } function read_int32(parser) { let value = parser.buf.readInt32LE(parser.pos); parser.pos += 4; return value; } function read_uint32(parser) { let value = parser.buf.readUInt32LE(parser.pos); parser.pos += 4; return value; } function read_uint32so(parser) { let value = read_uint8(parser); if (value === 0xff) { return read_uint32(parser); } return value; } function read_float(parser) { let value = parser.buf.readFloatLE(parser.pos); parser.pos += 4; return value; } function read_double(parser) { let value = parser.buf.readDoubleLE(parser.pos); parser.pos += 8; return value; } function read_string(parser) { let size = read_uint32so(parser); let data = parser.buf.slice(parser.pos, parser.pos + size).toString("utf-8"); parser.pos += size; return data; } function read_optional(parser, read_value) { let load = read_uint8(parser) !== 0; if (!load) { return null; } return read_value(parser); } function read_array(parser, read_item) { let size = read_uint32so(parser); let array = []; for (let i = 0; i < size; i++) { let item = read_item(parser); array.push(item); } return array; } function read_dict(parser, read_key, read_value) { let size = read_uint32so(parser); let mapping = new Map(); for (let i = 0; i < size; i++) { let key = read_key(parser); let value = read_value(parser); mapping.set(key, value); } return mapping; } function read_version(parser) { let major = read_uint16(parser); let minor = read_uint16(parser); let patch = read_uint16(parser); let developer = read_uint16(parser); return [major, minor, patch, developer]; } function read_frequency_size_richness(parser) { return { frequency: read_float(parser), size: read_float(parser), richness: read_float(parser), } } function read_autoplace_setting(parser) { return { treat_missing_as_default: read_bool(parser), settings: map_to_object(read_dict(parser, read_string, read_frequency_size_richness)), }; } function read_map_position(parser) { let x, y; let x_diff = read_int16(parser) / 256; if (x_diff === 0x7fff / 256) { x = read_int32(parser) / 256; y = read_int32(parser) / 256; } else { let y_diff = read_int16(parser) / 256; x = parser.last_position.x + x_diff; y = parser.last_position.y + y_diff; } parser.last_position.x = x; parser.last_position.x = y; return { x, y }; } function read_bounding_box(parser) { return { left_top: read_map_position(parser), right_bottom: read_map_position(parser), orientation: { x: read_int16(parser), y: read_int16(parser) }, }; } function read_cliff_settings(parser) { return { name: read_string(parser), elevation_0: read_float(parser), elevation_interval: read_float(parser), richness: read_float(parser), }; } function map_to_object(map) { @@ -183,391 +173,164 @@ function map_to_object(map) { return obj; } function read_map_gen_settings(parser) { return { terrain_segmentation: read_float(parser), water: read_float(parser), autoplace_controls: map_to_object(read_dict(parser, read_string, read_frequency_size_richness)), autoplace_settings: map_to_object(read_dict(parser, read_string, read_autoplace_setting)), default_enable_all_autoplace_controls: read_bool(parser), seed: read_uint32(parser), width: read_uint32(parser), height: read_uint32(parser), area_to_generate_at_start: read_bounding_box(parser), starting_area: read_float(parser), peaceful_mode: read_bool(parser), starting_points: read_array(parser, read_map_position), property_expression_names: map_to_object(read_dict(parser, read_string, read_string)), cliff_settings: read_cliff_settings(parser), }; } function read_pollution(parser) { let enabled; return { enabled: read_optional(parser, read_bool), diffusion_ratio: read_optional(parser, read_double), min_to_diffuse: read_optional(parser, read_double), ageing: read_optional(parser, read_double), expected_max_per_chunk: read_optional(parser, read_double), min_to_show_per_chunk: read_optional(parser, read_double), min_pollution_to_damage_trees: read_optional(parser, read_double), pollution_with_max_forest_damage: read_optional(parser, read_double), pollution_per_tree_damage: read_optional(parser, read_double), pollution_restored_per_tree_damage: read_optional(parser, read_double), max_pollution_to_restore_trees: read_optional(parser, read_double), enemy_attack_pollution_consumption_modifier: read_optional(parser, read_double), }; } function read_real_steering(parser) { return { radius: read_optional(parser, read_double), separation_factor: read_optional(parser, read_double), separation_force: read_optional(parser, read_double), force_unit_fuzzy_goto_behavior: read_optional(parser, read_bool), }; } function read_steering(parser) { return { default: read_real_steering(parser), moving: read_real_steering(parser), }; } function read_enemy_evolution(parser) { return { enabled: read_optional(parser, read_bool), time_factor: read_optional(parser, read_double), destroy_factor: read_optional(parser, read_double), pollution_factor: read_optional(parser, read_double), }; } function read_enemy_expansion(parser) { return { enabled: read_optional(parser, read_bool), max_expansion_distance: read_optional(parser, read_uint32), friendly_base_influence_radius: read_optional(parser, read_uint32), enemy_building_influence_radius: read_optional(parser, read_uint32), building_coefficient: read_optional(parser, read_double), other_base_coefficient: read_optional(parser, read_double), neighbouring_chunk_coefficient: read_optional(parser, read_double), neighbouring_base_chunk_coefficient: read_optional(parser, read_double), max_colliding_tiles_coefficient: read_optional(parser, read_double), settler_group_min_size: read_optional(parser, read_uint32), settler_group_max_size: read_optional(parser, read_uint32), min_expansion_cooldown: read_optional(parser, read_uint32), max_expansion_cooldown: read_optional(parser, read_uint32), }; } function read_unit_group(parser) { return { min_group_gathering_time: read_optional(parser, read_uint32), max_group_gathering_time: read_optional(parser, read_uint32), max_wait_time_for_late_members: read_optional(parser, read_uint32), max_group_radius: read_optional(parser, read_double), min_group_radius: read_optional(parser, read_double), max_member_speedup_when_behind: read_optional(parser, read_double), max_member_slowdown_when_ahead: read_optional(parser, read_double), max_group_slowdown_factor: read_optional(parser, read_double), max_group_member_fallback_factor: read_optional(parser, read_double), member_disown_distance: read_optional(parser, read_double), tick_tolerance_when_member_arrives: read_optional(parser, read_uint32), max_gathering_unit_groups: read_optional(parser, read_uint32), max_unit_group_size: read_optional(parser, read_uint32), }; } function read_path_finder(parser) { return { fwd2bwd_ratio: read_optional(parser, read_int32), goal_pressure_ratio: read_optional(parser, read_double), use_path_cache: read_optional(parser, read_bool), max_steps_worked_per_tick: read_optional(parser, read_double), max_work_done_per_tick: read_optional(parser, read_uint32), short_cache_size: read_optional(parser, read_uint32), long_cache_size: read_optional(parser, read_uint32), short_cache_min_cacheable_distance: read_optional(parser, read_double), short_cache_min_algo_steps_to_cache: read_optional(parser, read_uint32), long_cache_min_cacheable_distance: read_optional(parser, read_double), cache_max_connect_to_cache_steps_multiplier: read_optional(parser, read_uint32), cache_accept_path_start_distance_ratio: read_optional(parser, read_double), cache_accept_path_end_distance_ratio: read_optional(parser, read_double), negative_cache_accept_path_start_distance_ratio: read_optional(parser, read_double), negative_cache_accept_path_end_distance_ratio: read_optional(parser, read_double), cache_path_start_distance_rating_multiplier: read_optional(parser, read_double), cache_path_end_distance_rating_multiplier: read_optional(parser, read_double), stale_enemy_with_same_destination_collision_penalty: read_optional(parser, read_double), ignore_moving_enemy_collision_distance: read_optional(parser, read_double), enemy_with_different_destination_collision_penalty: read_optional(parser, read_double), general_entity_collision_penalty: read_optional(parser, read_double), general_entity_subsequent_collision_penalty: read_optional(parser, read_double), extended_collision_penalty: read_optional(parser, read_double), max_clients_to_accept_any_new_request: read_optional(parser, read_uint32), max_clients_to_accept_short_new_request: read_optional(parser, read_uint32), direct_distance_to_consider_short_request: read_optional(parser, read_uint32), short_request_max_steps: read_optional(parser, read_uint32), short_request_ratio: read_optional(parser, read_double), min_steps_to_check_path_find_termination: read_optional(parser, read_uint32), start_to_goal_cost_multiplier_to_terminate_path_find: read_optional(parser, read_double), overload_levels: read_optional(parser, (p) => read_array(p, read_uint32)), overload_multipliers: read_optional(parser, (p) => read_array(p, read_double)), negative_path_cache_delay_interval: read_optional(parser, read_uint32), }; } function read_difficulty_settings(parser) { return { recipe_difficulty: read_uint8(parser), technology_difficulty: read_uint8(parser), technology_price_multiplier: read_double(parser), research_queue_setting: ["always", "after-victory", "never"][read_uint8(parser)], }; } function read_map_settings(parser) { return { pollution: read_pollution(parser), steering: read_steering(parser), enemy_evolution: read_enemy_evolution(parser), enemy_expansion: read_enemy_expansion(parser), unit_group: read_unit_group(parser), path_finder: read_path_finder(parser), max_failed_behavior_count: read_uint32(parser), difficulty_settings: read_difficulty_settings(parser), }; } function decode(s) { @@ -579,27 +342,21 @@ function decode(s) { let buf = Buffer.from(s.slice(3, -3), "base64"); buf = zlib.inflateSync(buf); let parser = new Parser(buf); let data = { version: read_version(parser), unknown: read_uint8(parser), map_gen_settings: read_map_gen_settings(parser), map_settings: read_map_settings(parser), checksum: read_uint32(parser), }; if (parser.pos != buf.length) { return "data after end"; } return data; } let test = ` -
Hornwitser revised this gist
May 18, 2021 . 1 changed file with 19 additions and 19 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -110,11 +110,11 @@ function read_autoplace_setting(pos, buf) { let treat_missing_as_default = buf.readUInt8(pos) !== 0; pos += 1; let settings; ([pos, settings] = read_dict(pos, buf, read_string, read_frequency_size_richness)); return [pos, { treat_missing_as_default, settings: map_to_object(settings), }]; } @@ -238,8 +238,8 @@ function read_map_gen_settings(pos, buf) { function read_pollution(pos, buf) { let enabled; ([pos, enabled] = read_optional(pos, buf, read_bool)); let diffusion_ratio; ([pos, diffusion_ratio] = read_optional(pos, buf, read_double)); let min_to_diffuse; ([pos, min_to_diffuse] = read_optional(pos, buf, read_double)); let ageing; @@ -263,7 +263,7 @@ function read_pollution(pos, buf) { return [pos, { enabled, diffusion_ratio, min_to_diffuse, ageing, expected_max_per_chunk, @@ -284,14 +284,14 @@ function read_real_steering(pos, buf) { ([pos, separation_factor] = read_optional(pos, buf, read_double)); let separation_force; ([pos, separation_force] = read_optional(pos, buf, read_double)); let force_unit_fuzzy_goto_behavior; ([pos, force_unit_fuzzy_goto_behavior] = read_optional(pos, buf, read_bool)); return [pos, { radius, separation_factor, separation_force, force_unit_fuzzy_goto_behavior, }]; } @@ -355,6 +355,7 @@ function read_enemy_expansion(pos, buf) { ([pos, max_expansion_cooldown] = read_optional(pos, buf, read_uint32)); return [pos, { enabled, max_expansion_distance, friendly_base_influence_radius, enemy_building_influence_radius, @@ -478,8 +479,8 @@ function read_path_finder(pos, buf) { ([pos, start_to_goal_cost_multiplier_to_terminate_path_find] = read_optional(pos, buf, read_double)); let overload_levels; ([pos, overload_levels] = read_optional(pos, buf, (p, b) => read_array(p, b, read_uint32))); let overload_multipliers; ([pos, overload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double))); let negative_path_cache_delay_interval; ([pos, negative_path_cache_delay_interval] = read_optional(pos, buf, read_uint32)); @@ -515,12 +516,12 @@ function read_path_finder(pos, buf) { min_steps_to_check_path_find_termination, start_to_goal_cost_multiplier_to_terminate_path_find, overload_levels, overload_multipliers, negative_path_cache_delay_interval, }]; } function read_difficulty_settings(pos, buf) { let recipe_difficulty = buf.readUInt8(pos); pos += 1; let technology_difficulty = buf.readUInt8(pos); @@ -553,8 +554,8 @@ function read_map_settings(pos, buf) { ([pos, path_finder] = read_path_finder(pos, buf)); let max_failed_behavior_count = buf.readUInt32LE(pos); pos += 4; let difficulty_settings; ([pos, difficulty_settings] = read_difficulty_settings(pos, buf)); return [pos, { @@ -565,18 +566,17 @@ function read_map_settings(pos, buf) { unit_group, path_finder, max_failed_behavior_count, difficulty_settings, }]; } function decode(s) { s = s.replace(/[ \t\n\r]+/g, ""); if (!/>>>[0-9a-zA-Z\/+]+={0,3}<<</.test(s)) { return "Not a map exchange string"; } let buf = Buffer.from(s.slice(3, -3), "base64"); buf = zlib.inflateSync(buf); let pos = 0; -
Hornwitser created this gist
May 17, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,620 @@ const zlib = require("zlib"); const util = require("util"); function read_version(pos, buf) { let major = buf.readUInt16LE(pos); pos += 2; let minor = buf.readUInt16LE(pos); pos += 2; let patch = buf.readUInt16LE(pos); pos += 2; let developer = buf.readUInt16LE(pos); pos += 2; return [pos, [major, minor, patch, developer]]; } function read_bool(pos, buf) { let value = buf.readUint8(pos) !== 0; pos += 1; return [pos, value]; } function read_int32(pos, buf) { let value = buf.readInt32LE(pos); pos += 4; return [pos, value]; } function read_uint32(pos, buf) { let value = buf.readUInt32LE(pos); pos += 4; return [pos, value]; } function read_uint32so(pos, buf) { let value = buf.readUInt8(pos); pos += 1; if (value === 0xff) { let value = buf.readUInt32LE(pos); pos += 4; } return [pos, value]; } function read_optional(pos, buf, read) { let load = buf.readUint8(pos) !== 0; pos += 1; if (!load) { return [pos, null]; } return read(pos, buf); } function read_double(pos, buf) { let value = buf.readDoubleLE(pos); pos += 8; return [pos, value]; } function read_string(pos, buf) { let size; ([pos, size] = read_uint32so(pos, buf)); let data = buf.slice(pos, pos + size).toString("utf-8"); return [pos + size, data]; } function read_dict(pos, buf, read_key, read_value) { let size; ([pos, size] = read_uint32so(pos, buf)); let mapping = new Map(); for (let i = 0; i < size; i++) { let key, value; ([pos, key] = read_key(pos, buf)); ([pos, value] = read_value(pos, buf)); mapping.set(key, value); } return [pos, mapping]; } function read_array(pos, buf, read_item) { let size; ([pos, size] = read_uint32so(pos, buf)); let array = []; for (let i = 0; i < size; i++) { let item; ([pos, item] = read_item(pos, buf)); array.push(item); } return [pos, array]; } function read_frequency_size_richness(pos, buf) { let frequency = buf.readFloatLE(pos); pos += 4; let size = buf.readFloatLE(pos); pos += 4; let richness = buf.readFloatLE(pos); pos += 4; return [pos, { frequency, size, richness }]; } function read_autoplace_setting(pos, buf) { let treat_missing_as_default = buf.readUInt8(pos) !== 0; pos += 1; let settings; ([pos, settings] = read_frequency_size_richness(pos, buf)); return [pos, { treat_missing_as_default, settings, }]; } function read_map_position(pos, buf, state) { let x, y; let x_diff = buf.readInt16LE(pos) / 256; pos += 2; if (x_diff === 0x7fff / 256) { x = buf.readInt32LE(pos) / 256; pos += 4; y = buf.readInt32LE(pos) / 256; pos += 4; } else { let y_diff = buf.readInt16LE(pos) / 256; pos += 2; x = state.last_position.x + x_diff; y = state.last_position.y + y_diff; } state.last_position.x = x; state.last_position.x = y; return [pos, { x, y }] } function read_bounding_box(pos, buf, state) { let left_top; ([pos, left_top] = read_map_position(pos, buf, state)); let right_bottom; ([pos, right_bottom] = read_map_position(pos, buf, state)); let orientation; let x = buf.readInt16LE(pos); pos += 2; let y = buf.readInt16LE(pos); pos += 2; return [pos, { left_top, right_bottom, orientation: { x, y }, }]; } function read_cliff_settings(pos, buf) { let name; ([pos, name] = read_string(pos, buf)); let elevation_0 = buf.readFloatLE(pos); pos += 4; let elevation_interval = buf.readFloatLE(pos); pos += 4; let richness = buf.readFloatLE(pos); pos += 4; return [pos, { name, elevation_0, elevation_interval, richness, }]; } function map_to_object(map) { let obj = {}; for (let [key, value] of map) { obj[key] = value; } return obj; } function read_map_gen_settings(pos, buf) { const state = { last_position: { x: 0, y: 0 }, }; let terrain_segmentation = buf.readFloatLE(pos); pos += 4; let water = buf.readFloatLE(pos); pos += 4; let autoplace_controls; ([pos, autoplace_controls] = read_dict(pos, buf, read_string, read_frequency_size_richness)); let autoplace_settings; ([pos, autoplace_settings] = read_dict(pos, buf, read_string, read_autoplace_setting)); let default_enable_all_autoplace_controls = buf.readUInt8(pos) !== 0; pos += 1; let seed = buf.readUInt32LE(pos); pos += 4; let width = buf.readUInt32LE(pos); pos += 4; let height = buf.readUInt32LE(pos); pos += 4; let area_to_generate_at_start; ([pos, area_to_generate_at_start] = read_bounding_box(pos, buf, state)); let starting_area = buf.readFloatLE(pos); pos += 4; let peaceful_mode = buf.readUInt8(pos) !== 0; pos += 1; let starting_points; ([pos, starting_points] = read_array(pos, buf, (pos, buf) => read_map_position(pos, buf, state))); let property_expression_names; ([pos, property_expression_names] = read_dict(pos, buf, read_string, read_string)); let cliff_settings; ([pos, cliff_settings] = read_cliff_settings(pos, buf)); return [pos, { terrain_segmentation, water, autoplace_controls: map_to_object(autoplace_controls), autoplace_settings: map_to_object(autoplace_settings), default_enable_all_autoplace_controls, seed, width, height, area_to_generate_at_start, starting_area, peaceful_mode, starting_points, property_expression_names: map_to_object(property_expression_names), cliff_settings, }]; } function read_pollution(pos, buf) { let enabled; ([pos, enabled] = read_optional(pos, buf, read_bool)); let diffision_ratio; ([pos, diffision_ratio] = read_optional(pos, buf, read_double)); let min_to_diffuse; ([pos, min_to_diffuse] = read_optional(pos, buf, read_double)); let ageing; ([pos, ageing] = read_optional(pos, buf, read_double)); let expected_max_per_chunk; ([pos, expected_max_per_chunk] = read_optional(pos, buf, read_double)); let min_to_show_per_chunk; ([pos, min_to_show_per_chunk] = read_optional(pos, buf, read_double)); let min_pollution_to_damage_trees; ([pos, min_pollution_to_damage_trees] = read_optional(pos, buf, read_double)); let pollution_with_max_forest_damage; ([pos, pollution_with_max_forest_damage] = read_optional(pos, buf, read_double)); let pollution_per_tree_damage; ([pos, pollution_per_tree_damage] = read_optional(pos, buf, read_double)); let pollution_restored_per_tree_damage; ([pos, pollution_restored_per_tree_damage] = read_optional(pos, buf, read_double)); let max_pollution_to_restore_trees; ([pos, max_pollution_to_restore_trees] = read_optional(pos, buf, read_double)); let enemy_attack_pollution_consumption_modifier; ([pos, enemy_attack_pollution_consumption_modifier] = read_optional(pos, buf, read_double)); return [pos, { enabled, diffision_ratio, min_to_diffuse, ageing, expected_max_per_chunk, min_to_show_per_chunk, min_pollution_to_damage_trees, pollution_with_max_forest_damage, pollution_per_tree_damage, pollution_restored_per_tree_damage, max_pollution_to_restore_trees, enemy_attack_pollution_consumption_modifier, }]; } function read_real_steering(pos, buf) { let radius; ([pos, radius] = read_optional(pos, buf, read_double)); let separation_factor; ([pos, separation_factor] = read_optional(pos, buf, read_double)); let separation_force; ([pos, separation_force] = read_optional(pos, buf, read_double)); let force_unit_fuzzy_goto_behaviour; ([pos, force_unit_fuzzy_goto_behaviour] = read_optional(pos, buf, read_bool)); return [pos, { radius, separation_factor, separation_force, force_unit_fuzzy_goto_behaviour, }]; } function read_steering(pos, buf) { let default_; ([pos, default_] = read_real_steering(pos, buf)); let moving ([pos, moving] = read_real_steering(pos, buf)); return [pos, { default: default_, moving, }]; } function read_enemy_evolution(pos, buf) { let enabled; ([pos, enabled] = read_optional(pos, buf, read_bool)); let time_factor; ([pos, time_factor] = read_optional(pos, buf, read_double)); let destroy_factor; ([pos, destroy_factor] = read_optional(pos, buf, read_double)); let pollution_factor; ([pos, pollution_factor] = read_optional(pos, buf, read_double)); return [pos, { enabled, time_factor, destroy_factor, pollution_factor, }]; } function read_enemy_expansion(pos, buf) { let enabled; ([pos, enabled] = read_optional(pos, buf, read_bool)); let max_expansion_distance; ([pos, max_expansion_distance] = read_optional(pos, buf, read_uint32)); let friendly_base_influence_radius; ([pos, friendly_base_influence_radius] = read_optional(pos, buf, read_uint32)); let enemy_building_influence_radius; ([pos, enemy_building_influence_radius] = read_optional(pos, buf, read_uint32)); let building_coefficient; ([pos, building_coefficient] = read_optional(pos, buf, read_double)); let other_base_coefficient; ([pos, other_base_coefficient] = read_optional(pos, buf, read_double)); let neighbouring_chunk_coefficient; ([pos, neighbouring_chunk_coefficient] = read_optional(pos, buf, read_double)); let neighbouring_base_chunk_coefficient; ([pos, neighbouring_base_chunk_coefficient] = read_optional(pos, buf, read_double)); let max_colliding_tiles_coefficient; ([pos, max_colliding_tiles_coefficient] = read_optional(pos, buf, read_double)); let settler_group_min_size; ([pos, settler_group_min_size] = read_optional(pos, buf, read_uint32)); let settler_group_max_size; ([pos, settler_group_max_size] = read_optional(pos, buf, read_uint32)); let min_expansion_cooldown; ([pos, min_expansion_cooldown] = read_optional(pos, buf, read_uint32)); let max_expansion_cooldown; ([pos, max_expansion_cooldown] = read_optional(pos, buf, read_uint32)); return [pos, { max_expansion_distance, friendly_base_influence_radius, enemy_building_influence_radius, building_coefficient, other_base_coefficient, neighbouring_chunk_coefficient, neighbouring_base_chunk_coefficient, max_colliding_tiles_coefficient, settler_group_min_size, settler_group_max_size, min_expansion_cooldown, max_expansion_cooldown, }]; } function read_unit_group(pos, buf) { let min_group_gathering_time; ([pos, min_group_gathering_time] = read_optional(pos, buf, read_uint32)); let max_group_gathering_time; ([pos, max_group_gathering_time] = read_optional(pos, buf, read_uint32)); let max_wait_time_for_late_members; ([pos, max_wait_time_for_late_members] = read_optional(pos, buf, read_uint32)); let max_group_radius; ([pos, max_group_radius] = read_optional(pos, buf, read_double)); let min_group_radius; ([pos, min_group_radius] = read_optional(pos, buf, read_double)); let max_member_speedup_when_behind; ([pos, max_member_speedup_when_behind] = read_optional(pos, buf, read_double)); let max_member_slowdown_when_ahead; ([pos, max_member_slowdown_when_ahead] = read_optional(pos, buf, read_double)); let max_group_slowdown_factor; ([pos, max_group_slowdown_factor] = read_optional(pos, buf, read_double)); let max_group_member_fallback_factor; ([pos, max_group_member_fallback_factor] = read_optional(pos, buf, read_double)); let member_disown_distance; ([pos, member_disown_distance] = read_optional(pos, buf, read_double)); let tick_tolerance_when_member_arrives; ([pos, tick_tolerance_when_member_arrives] = read_optional(pos, buf, read_uint32)); let max_gathering_unit_groups; ([pos, max_gathering_unit_groups] = read_optional(pos, buf, read_uint32)); let max_unit_group_size; ([pos, max_unit_group_size] = read_optional(pos, buf, read_uint32)); return [pos, { min_group_gathering_time, max_group_gathering_time, max_wait_time_for_late_members, max_group_radius, min_group_radius, max_member_speedup_when_behind, max_member_slowdown_when_ahead, max_group_slowdown_factor, max_group_member_fallback_factor, member_disown_distance, tick_tolerance_when_member_arrives, max_gathering_unit_groups, max_unit_group_size, }]; } function read_path_finder(pos, buf) { let fwd2bwd_ratio; ([pos, fwd2bwd_ratio] = read_optional(pos, buf, read_int32)); let goal_pressure_ratio; ([pos, goal_pressure_ratio] = read_optional(pos, buf, read_double)); let use_path_cache; ([pos, use_path_cache] = read_optional(pos, buf, read_bool)); let max_steps_worked_per_tick; ([pos, max_steps_worked_per_tick] = read_optional(pos, buf, read_double)); let max_work_done_per_tick; ([pos, max_work_done_per_tick] = read_optional(pos, buf, read_uint32)); let short_cache_size; ([pos, short_cache_size] = read_optional(pos, buf, read_uint32)); let long_cache_size; ([pos, long_cache_size] = read_optional(pos, buf, read_uint32)); let short_cache_min_cacheable_distance; ([pos, short_cache_min_cacheable_distance] = read_optional(pos, buf, read_double)); let short_cache_min_algo_steps_to_cache; ([pos, short_cache_min_algo_steps_to_cache] = read_optional(pos, buf, read_uint32)); let long_cache_min_cacheable_distance; ([pos, long_cache_min_cacheable_distance] = read_optional(pos, buf, read_double)); let cache_max_connect_to_cache_steps_multiplier; ([pos, cache_max_connect_to_cache_steps_multiplier] = read_optional(pos, buf, read_uint32)); let cache_accept_path_start_distance_ratio; ([pos, cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double)); let cache_accept_path_end_distance_ratio; ([pos, cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double)); let negative_cache_accept_path_start_distance_ratio; ([pos, negative_cache_accept_path_start_distance_ratio] = read_optional(pos, buf, read_double)); let negative_cache_accept_path_end_distance_ratio; ([pos, negative_cache_accept_path_end_distance_ratio] = read_optional(pos, buf, read_double)); let cache_path_start_distance_rating_multiplier; ([pos, cache_path_start_distance_rating_multiplier] = read_optional(pos, buf, read_double)); let cache_path_end_distance_rating_multiplier; ([pos, cache_path_end_distance_rating_multiplier] = read_optional(pos, buf, read_double)); let stale_enemy_with_same_destination_collision_penalty; ([pos, stale_enemy_with_same_destination_collision_penalty] = read_optional(pos, buf, read_double)); let ignore_moving_enemy_collision_distance; ([pos, ignore_moving_enemy_collision_distance] = read_optional(pos, buf, read_double)); let enemy_with_different_destination_collision_penalty; ([pos, enemy_with_different_destination_collision_penalty] = read_optional(pos, buf, read_double)); let general_entity_collision_penalty; ([pos, general_entity_collision_penalty] = read_optional(pos, buf, read_double)); let general_entity_subsequent_collision_penalty; ([pos, general_entity_subsequent_collision_penalty] = read_optional(pos, buf, read_double)); let extended_collision_penalty; ([pos, extended_collision_penalty] = read_optional(pos, buf, read_double)); let max_clients_to_accept_any_new_request; ([pos, max_clients_to_accept_any_new_request] = read_optional(pos, buf, read_uint32)); let max_clients_to_accept_short_new_request; ([pos, max_clients_to_accept_short_new_request] = read_optional(pos, buf, read_uint32)); let direct_distance_to_consider_short_request; ([pos, direct_distance_to_consider_short_request] = read_optional(pos, buf, read_uint32)); let short_request_max_steps; ([pos, short_request_max_steps] = read_optional(pos, buf, read_uint32)); let short_request_ratio; ([pos, short_request_ratio] = read_optional(pos, buf, read_double)); let min_steps_to_check_path_find_termination; ([pos, min_steps_to_check_path_find_termination] = read_optional(pos, buf, read_uint32)); let start_to_goal_cost_multiplier_to_terminate_path_find; ([pos, start_to_goal_cost_multiplier_to_terminate_path_find] = read_optional(pos, buf, read_double)); let overload_levels; ([pos, overload_levels] = read_optional(pos, buf, (p, b) => read_array(p, b, read_uint32))); let oveload_multipliers; ([pos, oveload_multipliers] = read_optional(pos, buf, (p, b) => read_array(p, b, read_double))); let negative_path_cache_delay_interval; ([pos, negative_path_cache_delay_interval] = read_optional(pos, buf, read_uint32)); return [pos, { fwd2bwd_ratio, goal_pressure_ratio, use_path_cache, max_steps_worked_per_tick, max_work_done_per_tick, short_cache_size, long_cache_size, short_cache_min_cacheable_distance, short_cache_min_algo_steps_to_cache, long_cache_min_cacheable_distance, cache_max_connect_to_cache_steps_multiplier, cache_accept_path_start_distance_ratio, cache_accept_path_end_distance_ratio, negative_cache_accept_path_start_distance_ratio, negative_cache_accept_path_end_distance_ratio, cache_path_start_distance_rating_multiplier, cache_path_end_distance_rating_multiplier, stale_enemy_with_same_destination_collision_penalty, ignore_moving_enemy_collision_distance, enemy_with_different_destination_collision_penalty, general_entity_collision_penalty, general_entity_subsequent_collision_penalty, extended_collision_penalty, max_clients_to_accept_any_new_request, max_clients_to_accept_short_new_request, direct_distance_to_consider_short_request, short_request_max_steps, short_request_ratio, min_steps_to_check_path_find_termination, start_to_goal_cost_multiplier_to_terminate_path_find, overload_levels, oveload_multipliers, negative_path_cache_delay_interval, }]; } function read_difficulty(pos, buf) { let recipe_difficulty = buf.readUInt8(pos); pos += 1; let technology_difficulty = buf.readUInt8(pos); pos += 1; let technology_price_multiplier = buf.readDoubleLE(pos); pos += 8; let research_queue_setting = buf.readUInt8(pos); pos += 1; return [pos, { recipe_difficulty, technology_difficulty, technology_price_multiplier, research_queue_setting: ["always", "after-victory", "never"][research_queue_setting], }]; } function read_map_settings(pos, buf) { let pollution; ([pos, pollution] = read_pollution(pos, buf)); let steering; ([pos, steering] = read_steering(pos, buf)); let enemy_evolution; ([pos, enemy_evolution] = read_enemy_evolution(pos, buf)); let enemy_expansion; ([pos, enemy_expansion] = read_enemy_expansion(pos, buf)); let unit_group; ([pos, unit_group] = read_unit_group(pos, buf)); let path_finder; ([pos, path_finder] = read_path_finder(pos, buf)); let max_failed_behavior_count = buf.readUInt32LE(pos); pos += 4; let difficulty; ([pos, difficulty] = read_difficulty(pos, buf)); return [pos, { pollution, steering, enemy_evolution, enemy_expansion, unit_group, path_finder, max_failed_behavior_count, difficulty, }]; } function decode(s) { s = s.trim(); if (!/>>>[0-9a-zA-Z\/+\n]+<<</.test(s)) { return "Not a map exchange string"; } s = s.slice(3, -3).replace(/\n/g, ""); let buf = Buffer.from(s, "base64"); buf = zlib.inflateSync(buf); let pos = 0; let version; ([pos, version] = read_version(pos, buf)); pos += 1; // Discard unknown flag. let map_gen_settings; ([pos, map_gen_settings] = read_map_gen_settings(pos, buf)); let map_settings; ([pos, map_settings] = read_map_settings(pos, buf)); let checksum = buf.readUInt32LE(pos); pos += 4; if (pos != buf.length) { return "data after end"; } return { version, map_gen_settings, map_settings, checksum, } } let test = ` >>>eNp1Uz2IE1EQnsklmosoKVKcoGfkUhzChhCtgmSfNmJhIZhScLN5 0YXNbm5/wDsLU1xxhSDINdpo64k2YmEXsPFAQbSyOzkLBcE7PeQKIb7 Zt2+zxNzAzM77ZuabmfdYBITTIEV/trHRzGdN17ABBrrSgun2+9zTXI 9TkoJnTS/scM217BTKCtzhvWWtbfiUzACGUShvea4jGYYJQ84PXCdOi 5HA49wXY0SjEHIk9AzHCnuydkC4rMeXv65vDlbngXR0F8qjEanwtgQj KeAgYkGBxZKdM10n8Fxb83kQWM7NhhHebrQtw5/VatV6jWRxWkrX40s hd8zlRi+0A6tvW9zL16tRQe3kZEXPtfwg9PgEs3Zg3lT6WvVsJDnTtr pdgPKFVqt1kVZCxDulF5e+rKzrKBerstjZj5FhWyGXY+fBc3ZQKKtCc E45OzrK7r9TjmwaiBZxVp6NHRlcpSDi7q3ttVf7e038+3T345X2DR2v fS0u+We+0+xH6W0yiXn0kOS1WgUU55Yehz7r+P4dyQ8dZ6higczOega wdvUQYPGYOD65J0z5BKjRmoqmxLAbyR+1ybZyPumTe1QYnifyeTJvye QgoRSToXTZfYbslIoeH6eI+jqkZ+iMN9xUbd+k+k8MUvnvIdJ7TCAVN uUZCtSwk5hvM8k04j4/HFYn9phFdwmUtScweZI/o6SS3yLDkvhE9x4T LbLM2k9Y+AcYCOn/<<<`; console.log(util.inspect(decode(test), { depth: 20, colors: true }));