diff --git a/examples/frameworkServer/Dimension.snbt b/examples/frameworkServer/Dimension.snbt new file mode 100644 index 0000000..2b9092d --- /dev/null +++ b/examples/frameworkServer/Dimension.snbt @@ -0,0 +1,17 @@ +{ + piglin_safe: 0b, + natural: 1b, + ambient_light: 0.0f, + infiniburn: "minecraft:infiniburn_overworld", + respawn_anchor_works: 0b, + has_skylight: 1b, + bed_works: 1b, + effects: "minecraft:overworld", + has_raids: 1b, + min_y: 0, + height: 256, + logical_height: 256, + coordinate_scale: 1.0d, + ultrawarm: 0b, + has_ceiling: 0b +} \ No newline at end of file diff --git a/examples/frameworkServer/DimensionCodec.snbt b/examples/frameworkServer/DimensionCodec.snbt new file mode 100644 index 0000000..44ea6d5 --- /dev/null +++ b/examples/frameworkServer/DimensionCodec.snbt @@ -0,0 +1,2028 @@ +{ + "minecraft:dimension_type": { + type: "minecraft:dimension_type", + value: [ + { + name: "minecraft:overworld", + id: 0, + element: { + piglin_safe: 0b, + natural: 1b, + ambient_light: 0.0f, + infiniburn: "minecraft:infiniburn_overworld", + respawn_anchor_works: 0b, + has_skylight: 1b, + bed_works: 1b, + effects: "minecraft:overworld", + has_raids: 1b, + min_y: 0, + height: 256, + logical_height: 256, + coordinate_scale: 1.0d, + ultrawarm: 0b, + has_ceiling: 0b + } + } + ] + }, + "minecraft:worldgen/biome": { + type: "minecraft:worldgen/biome", + value: [ + { + name: "minecraft:ocean", + id: 0, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:plains", + id: 1, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.8f, + scale: 0.05f, + downfall: 0.4f, + category: "plains" + } + }, + { + name: "minecraft:desert", + id: 2, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 2.0f, + scale: 0.05f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:mountains", + id: 3, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:forest", + id: 4, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.2f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga", + id: 5, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:swamp", + id: 6, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "swamp", + sky_color: 7907327, + foliage_color: 6975545, + water_fog_color: 2302743, + fog_color: 12638463, + water_color: 6388580, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.2f, + temperature: 0.8f, + scale: 0.1f, + downfall: 0.9f, + category: "swamp" + } + }, + { + name: "minecraft:river", + id: 7, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.5f, + temperature: 0.5f, + scale: 0.0f, + downfall: 0.5f, + category: "river" + } + }, + { + name: "minecraft:nether_wastes", + id: 8, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.nether_wastes", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.nether_wastes.loop", + additions_sound: { + sound: "minecraft:ambient.nether_wastes.additions", + tick_chance: 0.0111d + }, + water_fog_color: 329011, + fog_color: 3344392, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.nether_wastes.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:the_end", + id: 9, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:frozen_ocean", + id: 10, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.0f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean", + temperature_modifier: "frozen" + } + }, + { + name: "minecraft:frozen_river", + id: 11, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.5f, + temperature: 0.0f, + scale: 0.0f, + downfall: 0.5f, + category: "river" + } + }, + { + name: "minecraft:snowy_tundra", + id: 12, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.0f, + scale: 0.05f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:snowy_mountains", + id: 13, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.0f, + scale: 0.3f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:mushroom_fields", + id: 14, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.9f, + scale: 0.3f, + downfall: 1.0f, + category: "mushroom" + } + }, + { + name: "minecraft:mushroom_field_shore", + id: 15, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.9f, + scale: 0.025f, + downfall: 1.0f, + category: "mushroom" + } + }, + { + name: "minecraft:beach", + id: 16, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.8f, + scale: 0.025f, + downfall: 0.4f, + category: "beach" + } + }, + { + name: "minecraft:desert_hills", + id: 17, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:wooded_hills", + id: 18, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.7f, + scale: 0.3f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga_hills", + id: 19, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.25f, + scale: 0.3f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:mountain_edge", + id: 20, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.8f, + temperature: 0.2f, + scale: 0.3f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:jungle", + id: 21, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:jungle_hills", + id: 22, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.95f, + scale: 0.3f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:jungle_edge", + id: 23, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.8f, + category: "jungle" + } + }, + { + name: "minecraft:deep_ocean", + id: 24, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:stone_shore", + id: 25, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.2f, + scale: 0.8f, + downfall: 0.3f, + category: "none" + } + }, + { + name: "minecraft:snowy_beach", + id: 26, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.0f, + temperature: 0.05f, + scale: 0.025f, + downfall: 0.3f, + category: "beach" + } + }, + { + name: "minecraft:birch_forest", + id: 27, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.6f, + scale: 0.2f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:birch_forest_hills", + id: 28, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.6f, + scale: 0.3f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:dark_forest", + id: 29, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "dark_forest", + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.2f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:snowy_taiga", + id: 30, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: -0.5f, + scale: 0.2f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:snowy_taiga_hills", + id: 31, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: -0.5f, + scale: 0.3f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:giant_tree_taiga", + id: 32, + element: { + precipitation: "rain", + effects: { + sky_color: 8168447, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.3f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:giant_tree_taiga_hills", + id: 33, + element: { + precipitation: "rain", + effects: { + sky_color: 8168447, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.3f, + scale: 0.3f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:wooded_mountains", + id: 34, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:savanna", + id: 35, + element: { + precipitation: "none", + effects: { + sky_color: 7711487, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 1.2f, + scale: 0.05f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:savanna_plateau", + id: 36, + element: { + precipitation: "none", + effects: { + sky_color: 7776511, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 1.0f, + scale: 0.025f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:badlands", + id: 37, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:wooded_badlands_plateau", + id: 38, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 2.0f, + scale: 0.025f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:badlands_plateau", + id: 39, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.5f, + temperature: 2.0f, + scale: 0.025f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:small_end_islands", + id: 40, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_midlands", + id: 41, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_highlands", + id: 42, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:end_barrens", + id: 43, + element: { + precipitation: "none", + effects: { + sky_color: 0, + water_fog_color: 329011, + fog_color: 10518688, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "the_end" + } + }, + { + name: "minecraft:warm_ocean", + id: 44, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 270131, + fog_color: 12638463, + water_color: 4445678, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:lukewarm_ocean", + id: 45, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 267827, + fog_color: 12638463, + water_color: 4566514, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:cold_ocean", + id: 46, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.0f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_warm_ocean", + id: 47, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 270131, + fog_color: 12638463, + water_color: 4445678, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_lukewarm_ocean", + id: 48, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 267827, + fog_color: 12638463, + water_color: 4566514, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_cold_ocean", + id: 49, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean" + } + }, + { + name: "minecraft:deep_frozen_ocean", + id: 50, + element: { + precipitation: "rain", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 3750089, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -1.8f, + temperature: 0.5f, + scale: 0.1f, + downfall: 0.5f, + category: "ocean", + temperature_modifier: "frozen" + } + }, + { + name: "minecraft:the_void", + id: 127, + element: { + precipitation: "none", + effects: { + sky_color: 8103167, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.5f, + scale: 0.2f, + downfall: 0.5f, + category: "none" + } + }, + { + name: "minecraft:sunflower_plains", + id: 129, + element: { + precipitation: "rain", + effects: { + sky_color: 7907327, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.125f, + temperature: 0.8f, + scale: 0.05f, + downfall: 0.4f, + category: "plains" + } + }, + { + name: "minecraft:desert_lakes", + id: 130, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.225f, + temperature: 2.0f, + scale: 0.25f, + downfall: 0.0f, + category: "desert" + } + }, + { + name: "minecraft:gravelly_mountains", + id: 131, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:flower_forest", + id: 132, + element: { + precipitation: "rain", + effects: { + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.7f, + scale: 0.4f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:taiga_mountains", + id: 133, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3f, + temperature: 0.25f, + scale: 0.4f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:swamp_hills", + id: 134, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "swamp", + sky_color: 7907327, + foliage_color: 6975545, + water_fog_color: 2302743, + fog_color: 12638463, + water_color: 6388580, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: -0.1f, + temperature: 0.8f, + scale: 0.3f, + downfall: 0.9f, + category: "swamp" + } + }, + { + name: "minecraft:ice_spikes", + id: 140, + element: { + precipitation: "snow", + effects: { + sky_color: 8364543, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.425f, + temperature: 0.0f, + scale: 0.45000002f, + downfall: 0.5f, + category: "icy" + } + }, + { + name: "minecraft:modified_jungle", + id: 149, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.95f, + scale: 0.4f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:modified_jungle_edge", + id: 151, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.95f, + scale: 0.4f, + downfall: 0.8f, + category: "jungle" + } + }, + { + name: "minecraft:tall_birch_forest", + id: 155, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.6f, + scale: 0.4f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:tall_birch_hills", + id: 156, + element: { + precipitation: "rain", + effects: { + sky_color: 8037887, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.55f, + temperature: 0.6f, + scale: 0.5f, + downfall: 0.6f, + category: "forest" + } + }, + { + name: "minecraft:dark_forest_hills", + id: 157, + element: { + precipitation: "rain", + effects: { + grass_color_modifier: "dark_forest", + sky_color: 7972607, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.7f, + scale: 0.4f, + downfall: 0.8f, + category: "forest" + } + }, + { + name: "minecraft:snowy_taiga_mountains", + id: 158, + element: { + precipitation: "snow", + effects: { + sky_color: 8625919, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4020182, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3f, + temperature: -0.5f, + scale: 0.4f, + downfall: 0.4f, + category: "taiga" + } + }, + { + name: "minecraft:giant_spruce_taiga", + id: 160, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:giant_spruce_taiga_hills", + id: 161, + element: { + precipitation: "rain", + effects: { + sky_color: 8233983, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.2f, + temperature: 0.25f, + scale: 0.2f, + downfall: 0.8f, + category: "taiga" + } + }, + { + name: "minecraft:modified_gravelly_mountains", + id: 162, + element: { + precipitation: "rain", + effects: { + sky_color: 8233727, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.0f, + temperature: 0.2f, + scale: 0.5f, + downfall: 0.3f, + category: "extreme_hills" + } + }, + { + name: "minecraft:shattered_savanna", + id: 163, + element: { + precipitation: "none", + effects: { + sky_color: 7776767, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.3625f, + temperature: 1.1f, + scale: 1.225f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:shattered_savanna_plateau", + id: 164, + element: { + precipitation: "none", + effects: { + sky_color: 7776511, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 1.05f, + temperature: 1.0f, + scale: 1.2125001f, + downfall: 0.0f, + category: "savanna" + } + }, + { + name: "minecraft:eroded_badlands", + id: 165, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:modified_wooded_badlands_plateau", + id: 166, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:modified_badlands_plateau", + id: 167, + element: { + precipitation: "none", + effects: { + sky_color: 7254527, + grass_color: 9470285, + foliage_color: 10387789, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 2.0f, + scale: 0.3f, + downfall: 0.0f, + category: "mesa" + } + }, + { + name: "minecraft:bamboo_jungle", + id: 168, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 0.95f, + scale: 0.2f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:bamboo_jungle_hills", + id: 169, + element: { + precipitation: "rain", + effects: { + sky_color: 7842047, + water_fog_color: 329011, + fog_color: 12638463, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.cave", + block_search_extent: 8 + } + }, + depth: 0.45f, + temperature: 0.95f, + scale: 0.3f, + downfall: 0.9f, + category: "jungle" + } + }, + { + name: "minecraft:soul_sand_valley", + id: 170, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.soul_sand_valley", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.soul_sand_valley.loop", + additions_sound: { + sound: "minecraft:ambient.soul_sand_valley.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.00625f, + options: { + type: "minecraft:ash" + } + }, + water_fog_color: 329011, + fog_color: 1787717, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.soul_sand_valley.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:crimson_forest", + id: 171, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.crimson_forest", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.crimson_forest.loop", + additions_sound: { + sound: "minecraft:ambient.crimson_forest.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.025f, + options: { + type: "minecraft:crimson_spore" + } + }, + water_fog_color: 329011, + fog_color: 3343107, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.crimson_forest.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:warped_forest", + id: 172, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.warped_forest", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.warped_forest.loop", + additions_sound: { + sound: "minecraft:ambient.warped_forest.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.01428f, + options: { + type: "minecraft:warped_spore" + } + }, + water_fog_color: 329011, + fog_color: 1705242, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.warped_forest.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + }, + { + name: "minecraft:basalt_deltas", + id: 173, + element: { + precipitation: "none", + effects: { + music: { + replace_current_music: 0b, + max_delay: 24000, + sound: "minecraft:music.nether.basalt_deltas", + min_delay: 12000 + }, + sky_color: 7254527, + ambient_sound: "minecraft:ambient.basalt_deltas.loop", + additions_sound: { + sound: "minecraft:ambient.basalt_deltas.additions", + tick_chance: 0.0111d + }, + particle: { + probability: 0.118093334f, + options: { + type: "minecraft:white_ash" + } + }, + water_fog_color: 4341314, + fog_color: 6840176, + water_color: 4159204, + mood_sound: { + tick_delay: 6000, + offset: 2.0d, + sound: "minecraft:ambient.basalt_deltas.mood", + block_search_extent: 8 + } + }, + depth: 0.1f, + temperature: 2.0f, + scale: 0.2f, + downfall: 0.0f, + category: "nether" + } + } + ] + } +} \ No newline at end of file diff --git a/examples/frameworkServer/main.go b/examples/frameworkServer/main.go new file mode 100644 index 0000000..c3f2a09 --- /dev/null +++ b/examples/frameworkServer/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "container/list" + _ "embed" + "log" + "sync" + + "github.com/Tnze/go-mc/chat" + "github.com/Tnze/go-mc/data/packetid" + "github.com/Tnze/go-mc/nbt" + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/server" + "github.com/google/uuid" +) + +type MyServer struct { + PlayerList *list.List + PlayerListLock sync.Mutex + server.MojangLoginHandler + Settings struct { + Name string + MaxPlayer int + MOTD chat.Message + } +} + +func main() { + var ms MyServer + ms.PlayerList = list.New() + ms.MojangLoginHandler.OnlineMode = true + ms.MojangLoginHandler.Threshold = 256 + + ms.Settings.Name = "MyServer" + ms.Settings.MaxPlayer = 20 + ms.Settings.MOTD = chat.Message{Text: "A Minecraft Server ", Extra: []chat.Message{{Text: "Powered by go-mc", Color: "yellow"}}} + + s := server.Server{ + ListPingHandler: &ms, + LoginHandler: &ms, + GamePlay: &ms, + } + if err := s.Listen(":25565"); err != nil { + log.Fatalf("Listen error: %v", err) + } +} + +func (m *MyServer) Name() string { + return m.Settings.Name +} + +func (m *MyServer) Protocol() int { + return server.ProtocolVersion +} + +func (m *MyServer) MaxPlayer() int { + return m.Settings.MaxPlayer +} + +func (m *MyServer) OnlinePlayer() int { + m.PlayerListLock.Lock() + defer m.PlayerListLock.Unlock() + return m.PlayerList.Len() +} + +func (m *MyServer) PlayerSamples() (sample []server.PlayerSample) { + m.PlayerListLock.Lock() + defer m.PlayerListLock.Unlock() + // get first 10 players + sample = make([]server.PlayerSample, 0, 10) + v := m.PlayerList.Front() + for i := 0; i < 10; i++ { + if v == nil { + break + } + sample = append(sample, v.Value.(server.PlayerSample)) + v = v.Next() + } + return +} + +func (m *MyServer) Description() chat.Message { + return m.Settings.MOTD +} + +func (m *MyServer) AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) { + // Add player into PlayerList + m.PlayerListLock.Lock() + elem := m.PlayerList.PushBack(server.PlayerSample{ + Name: name, + ID: id, + }) + m.PlayerListLock.Unlock() + defer func() { + m.PlayerListLock.Lock() + defer m.PlayerListLock.Unlock() + // remove player in PlayerList + m.PlayerList.Remove(elem) + }() + + if err := m.joinGame(conn); err != nil { + log.Printf("Write packet fail: %v", err) + return + } + if err := m.playerPositionAndLook(conn); err != nil { + log.Printf("Write packet fail: %v", err) + return + } + + var p pk.Packet + for { + err := conn.ReadPacket(&p) + if err != nil { + log.Printf("Read packet fail: %v", err) + break + } + + log.Printf("Read packet: %#X", p.ID) + } +} + +//go:embed DimensionCodec.snbt +var dimensionCodecSNBT string + +//go:embed Dimension.snbt +var dimensionSNBT string + +func (m *MyServer) joinGame(conn *net.Conn) error { + return conn.WritePacket(pk.Marshal(packetid.Login, + pk.Int(0), // EntityID + pk.Boolean(false), // Is hardcore + pk.UnsignedByte(1), // Gamemode + pk.Byte(1), // Previous Gamemode + pk.VarInt(1), // World Count + pk.Ary{Len: 1, Ary: []pk.Identifier{"world"}}, // World Names + pk.NBT(nbt.StringifiedMessage(dimensionCodecSNBT)), // Dimension codec + pk.NBT(nbt.StringifiedMessage(dimensionSNBT)), // Dimension + pk.Identifier("world"), // World Name + pk.Long(0), // Hashed Seed + pk.VarInt(m.Settings.MaxPlayer), // Max Players + pk.VarInt(15), // View Distance + pk.Boolean(false), // Reduced Debug Info + pk.Boolean(true), // Enable respawn screen + pk.Boolean(false), // Is Debug + pk.Boolean(true), // Is Flat + )) +} + +func (m *MyServer) playerPositionAndLook(conn *net.Conn) error { + return conn.WritePacket(pk.Marshal(packetid.PositionClientbound, + // https://wiki.vg/index.php?title=Protocol&oldid=16067#Player_Position_And_Look_.28clientbound.29 + pk.Double(0), pk.Double(0), pk.Double(0), // XYZ + pk.Float(0), pk.Float(0), // Yaw Pitch + pk.Byte(0), // flag + pk.VarInt(0), // TP ID + pk.Boolean(false), // Dismount vehicle + )) +} diff --git a/examples/simpleServer1.17.1/main.go b/examples/simpleServer1.17.1/main.go index dbf021c..773ddb1 100644 --- a/examples/simpleServer1.17.1/main.go +++ b/examples/simpleServer1.17.1/main.go @@ -148,7 +148,7 @@ func handshake(conn net.Conn) (protocol, intention int32, err error) { // loginSuccess send LoginSuccess packet to client func loginSuccess(conn net.Conn, name string, uuid uuid.UUID) error { return conn.WritePacket(pk.Marshal(0x02, - pk.UUID(uuid), //uuid as string with hyphens + pk.UUID(uuid), pk.String(name), )) } diff --git a/net/conn.go b/net/conn.go index 3a8f41d..b7d3b28 100644 --- a/net/conn.go +++ b/net/conn.go @@ -2,7 +2,6 @@ package net import ( - "bufio" "crypto/cipher" "io" "net" @@ -27,9 +26,10 @@ func ListenMC(addr string) (*Listener, error) { func (l Listener) Accept() (Conn, error) { conn, err := l.Listener.Accept() return Conn{ - Socket: conn, - Reader: bufio.NewReader(conn), - Writer: conn, + Socket: conn, + Reader: conn, + Writer: conn, + threshold: -1, }, err } @@ -46,9 +46,10 @@ type Conn struct { func DialMC(addr string) (*Conn, error) { conn, err := net.Dial("tcp", addr) return &Conn{ - Socket: conn, - Reader: conn, - Writer: conn, + Socket: conn, + Reader: conn, + Writer: conn, + threshold: -1, }, err } @@ -56,9 +57,10 @@ func DialMC(addr string) (*Conn, error) { func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) { conn, err := net.DialTimeout("tcp", addr, timeout) return &Conn{ - Socket: conn, - Reader: conn, - Writer: conn, + Socket: conn, + Reader: conn, + Writer: conn, + threshold: -1, }, err } @@ -66,13 +68,14 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) { // Helps you modify the connection process (eg. using DialContext). func WrapConn(conn net.Conn) *Conn { return &Conn{ - Socket: conn, - Reader: conn, - Writer: conn, + Socket: conn, + Reader: conn, + Writer: conn, + threshold: -1, } } -//Close close the connection +//Close the connection func (c *Conn) Close() error { return c.Socket.Close() } // ReadPacket read a Packet from Conn. @@ -80,7 +83,7 @@ func (c *Conn) ReadPacket(p *pk.Packet) error { return p.UnPack(c.Reader, c.threshold) } -//WritePacket write a Packet to Conn. +// WritePacket write a Packet to Conn. func (c *Conn) WritePacket(p pk.Packet) error { return p.Pack(c.Writer, c.threshold) } @@ -99,8 +102,8 @@ func (c *Conn) SetCipher(ecoStream, decoStream cipher.Stream) { } // SetThreshold set threshold to Conn. -// The data packet with length longer then threshold -// will be compress when sending. +// The data packet with length equal or longer then threshold +// will be compressed when sending. func (c *Conn) SetThreshold(t int) { c.threshold = t } diff --git a/net/packet/packet.go b/net/packet/packet.go index 9221038..22e239c 100644 --- a/net/packet/packet.go +++ b/net/packet/packet.go @@ -36,42 +36,75 @@ func (p Packet) Scan(fields ...FieldDecoder) error { // Pack 打包一个数据包 func (p *Packet) Pack(w io.Writer, threshold int) error { - var content bytes.Buffer - if _, err := VarInt(p.ID).WriteTo(&content); err != nil { - panic(err) - } - if _, err := content.Write(p.Data); err != nil { - panic(err) - } - if threshold > 0 { //是否启用了压缩 - rawLen := content.Len() - uncompressedLen := VarInt(rawLen) - if rawLen > threshold { //是否需要压缩 - compress(&content) - } else { - uncompressedLen = 0 - } - - uncompressedLenLen, _ := uncompressedLen.WriteTo(io.Discard) - if _, err := VarInt(uncompressedLenLen + int64(rawLen)).WriteTo(w); err != nil { - return err - } - - if _, err := uncompressedLen.WriteTo(w); err != nil { - return err - } - if _, err := content.WriteTo(w); err != nil { - return err - } + if threshold >= 0 { + return p.withCompression(w) } else { - if _, err := VarInt(content.Len()).WriteTo(w); err != nil { - return err - } - if _, err := content.WriteTo(w); err != nil { - return err - } + return p.withoutCompression(w) + } +} + +func (p *Packet) withoutCompression(w io.Writer) error { + var buf [5]byte + buffer := bytes.NewBuffer(buf[:0]) + n, err := VarInt(p.ID).WriteTo(buffer) + if err != nil { + panic(err) + } + // Length + _, err = VarInt(int(n) + len(p.Data)).WriteTo(w) + if err != nil { + return err + } + // Packet ID + _, err = buffer.WriteTo(w) + if err != nil { + return err + } + // Data + _, err = w.Write(p.Data) + if err != nil { + return err + } + return nil +} + +func (p *Packet) withCompression(w io.Writer) error { + var buff bytes.Buffer + zw := zlib.NewWriter(&buff) + n1, err := VarInt(p.ID).WriteTo(zw) + if err != nil { + return err + } + n2, err := zw.Write(p.Data) + if err != nil { + return err + } + err = zw.Close() + if err != nil { + return err } + var dataLength bytes.Buffer + n3, err := VarInt(int(n1) + n2).WriteTo(&dataLength) + if err != nil { + return err + } + + // Packet Length + _, err = VarInt(int(n3) + buff.Len()).WriteTo(w) + if err != nil { + return err + } + // Data Length + _, err = dataLength.WriteTo(w) + if err != nil { + return err + } + // PacketID + Data + _, err = buff.WriteTo(w) + if err != nil { + return err + } return nil } @@ -91,7 +124,7 @@ func (p *Packet) UnPack(r io.Reader, threshold int) error { buffer := bytes.NewBuffer(buf) //解压数据 - if threshold > 0 { + if threshold >= 0 { if err := unCompress(buffer); err != nil { return err } @@ -116,7 +149,9 @@ func unCompress(data *bytes.Buffer) error { } var uncompressedData []byte - if sizeUncompressed != 0 { // != 0 means compressed, let's decompress + if sizeUncompressed == 0 { + uncompressedData = data.Bytes()[1:] + } else { // != 0 means compressed, let's decompress uncompressedData = make([]byte, sizeUncompressed) r, err := zlib.NewReader(reader) if err != nil { @@ -127,23 +162,7 @@ func unCompress(data *bytes.Buffer) error { if err != nil { return fmt.Errorf("decompress fail: %v", err) } - } else { - uncompressedData = data.Bytes()[1:] } *data = *bytes.NewBuffer(uncompressedData) return nil } - -// compress 压缩数据 -func compress(data *bytes.Buffer) { - var b bytes.Buffer - w := zlib.NewWriter(&b) - if _, err := data.WriteTo(w); err != nil { - panic(err) - } - if err := w.Close(); err != nil { - panic(err) - } - *data = b - return -} diff --git a/server/auth/auth.go b/server/auth/auth.go new file mode 100644 index 0000000..e077ebb --- /dev/null +++ b/server/auth/auth.go @@ -0,0 +1,208 @@ +package auth + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/Tnze/go-mc/data/packetid" + "io/ioutil" + "net/http" + "strings" + + "github.com/Tnze/go-mc/net" + "github.com/Tnze/go-mc/net/CFB8" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/google/uuid" +) + +const verifyTokenLen = 16 + +//Encrypt a connection, with authentication +func Encrypt(conn *net.Conn, name string) (*Resp, error) { + //generate keys + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + return nil, err + } + + publicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + if err != nil { + return nil, err + } + + //encryption request + VT1, err := encryptionRequest(conn, publicKey) + if err != nil { + return nil, err + } + + //encryption response + ESharedSecret, EVerifyToken, err := encryptionResponse(conn) + if err != nil { + return nil, err + } + + //encryption the connection + SharedSecret, err := rsa.DecryptPKCS1v15(rand.Reader, key, ESharedSecret) + if err != nil { + return nil, err + } + VT2, err := rsa.DecryptPKCS1v15(rand.Reader, key, EVerifyToken) + if err != nil { + return nil, err + } + + //confirm the verify token + if !bytes.Equal(VT1, VT2) { + return nil, errors.New("verify token not match") + } + + block, err := aes.NewCipher(SharedSecret) + if err != nil { + return nil, errors.New("load aes encryption key fail") + } + + conn.SetCipher( //启用加密 + CFB8.NewCFB8Encrypt(block, SharedSecret), + CFB8.NewCFB8Decrypt(block, SharedSecret)) + + hash := authDigest("", SharedSecret, publicKey) + resp, err := authentication(name, hash) //auth + if err != nil { + return nil, errors.New("auth servers down") + } + + return resp, nil +} + +func encryptionRequest(conn *net.Conn, publicKey []byte) ([]byte, error) { + var verifyToken [verifyTokenLen]byte + _, err := rand.Read(verifyToken[:]) + if err != nil { + return nil, err + } + err = conn.WritePacket(pk.Marshal( + packetid.EncryptionBeginClientbound, + pk.String(""), + pk.ByteArray(publicKey), + pk.ByteArray(verifyToken[:]), + )) + return verifyToken[:], err +} + +func encryptionResponse(conn *net.Conn) ([]byte, []byte, error) { + var p pk.Packet + err := conn.ReadPacket(&p) + if err != nil { + return nil, nil, err + } + if p.ID != packetid.EncryptionBeginServerbound { + return nil, nil, fmt.Errorf("0x%02X is not Encryption Response", p.ID) + } + + var SharedSecret, VerifyToken pk.ByteArray + if err = p.Scan(&SharedSecret, &VerifyToken); err != nil { + return nil, nil, err + } + + return SharedSecret, VerifyToken, nil +} + +func authentication(name, hash string) (*Resp, error) { + resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + hash) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var Resp Resp + err = json.Unmarshal(body, &Resp) + + return &Resp, err +} + +// authDigest computes a special SHA-1 digest required for Minecraft web +// authentication on Premium servers (online-mode=true). +// Source: http://wiki.vg/Protocol_Encryption#Server +// +// Also many, many thanks to SirCmpwn and his wonderful gist (C#): +// https://gist.github.com/SirCmpwn/404223052379e82f91e6 +func authDigest(serverID string, sharedSecret, publicKey []byte) string { + h := sha1.New() + h.Write([]byte(serverID)) + h.Write(sharedSecret) + h.Write(publicKey) + hash := h.Sum(nil) + + // Check for negative hashes + negative := (hash[0] & 0x80) == 0x80 + if negative { + hash = twosComplement(hash) + } + + // Trim away zeroes + res := strings.TrimLeft(fmt.Sprintf("%x", hash), "0") + if negative { + res = "-" + res + } + + return res +} + +// little endian +func twosComplement(p []byte) []byte { + carry := true + for i := len(p) - 1; i >= 0; i-- { + p[i] = byte(^p[i]) + if carry { + carry = p[i] == 0xff + p[i]++ + } + } + return p +} + +//Resp is the response of authentication +type Resp struct { + Name string + ID uuid.UUID + Properties [1]struct { + Name, Value, Signature string + } +} + +//Texture includes player's skin and cape +type Texture struct { + TimeStamp int64 `json:"timestamp"` + ID uuid.UUID `json:"profileId"` + Name string `json:"profileName"` + Textures struct { + SKIN, CAPE struct { + URL string `json:"url"` + } + } `json:"textures"` +} + +//Texture unmarshal the base64 encoded texture of Resp +func (r *Resp) Texture() (t Texture, err error) { + var texture []byte + texture, err = base64.StdEncoding.DecodeString(r.Properties[0].Value) + if err != nil { + return + } + + err = json.Unmarshal(texture, &t) + return +} diff --git a/server/auth/auth_test.go b/server/auth/auth_test.go new file mode 100644 index 0000000..1044470 --- /dev/null +++ b/server/auth/auth_test.go @@ -0,0 +1,58 @@ +package auth + +import ( + "encoding/json" + "testing" + + "github.com/google/uuid" +) + +func TestResp(t *testing.T) { + var resp Resp + err := json.Unmarshal([]byte(`{"id":"853c80ef3c3749fdaa49938b674adae6","name":"jeb_","properties":[{"name":"textures","value":"eyJ0aW1lc3RhbXAiOjE1NTk1NDM5MzMwMjUsInByb2ZpbGVJZCI6Ijg1M2M4MGVmM2MzNzQ5ZmRhYTQ5OTM4YjY3NGFkYWU2IiwicHJvZmlsZU5hbWUiOiJqZWJfIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdmZDliYTQyYTdjODFlZWVhMjJmMTUyNDI3MWFlODVhOGUwNDVjZTBhZjVhNmFlMTZjNjQwNmFlOTE3ZTY4YjUifSwiQ0FQRSI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzU3ODZmZTk5YmUzNzdkZmI2ODU4ODU5ZjkyNmM0ZGJjOTk1NzUxZTkxY2VlMzczNDY4YzVmYmY0ODY1ZTcxNTEifX19"}]}`), &resp) + if err != nil { + panic(err) + } + wantID := uuid.Must(uuid.Parse("853c80ef3c3749fdaa49938b674adae6")) + + //check UUID + if resp.ID != wantID { + t.Errorf("uuid doesn't match: %v, want %s", resp.ID, wantID) + } + + //check name + if resp.Name != "jeb_" { + t.Errorf("name doesn't match: %s, want %s", resp.Name, "jeb_") + } + + //check texture + texture, err := resp.Texture() + if err != nil { + t.Fatal(err) + } + + t.Log(texture.TimeStamp) + + if texture.ID != wantID { + t.Errorf("uuid doesn't match: %v, want %s", texture.ID, wantID) + } + + if texture.Name != "jeb_" { + t.Errorf("name doesn't match: %s, want %s", texture.Name, "jeb_") + } + + const ( + wantSKIN = "http://textures.minecraft.net/texture/7fd9ba42a7c81eeea22f1524271ae85a8e045ce0af5a6ae16c6406ae917e68b5" + wantCAPE = "http://textures.minecraft.net/texture/5786fe99be377dfb6858859f926c4dbc995751e91cee373468c5fbf4865e7151" + ) + if texture.Textures.SKIN.URL != wantSKIN { + t.Errorf("skin url not match: %s, want %s", + texture.Textures.SKIN.URL, + wantSKIN) + } + if texture.Textures.CAPE.URL != wantCAPE { + t.Errorf("cape url not match: %s, want %s", + texture.Textures.CAPE.URL, + wantCAPE) + } +} diff --git a/server/gameplay.go b/server/gameplay.go new file mode 100644 index 0000000..4cfe553 --- /dev/null +++ b/server/gameplay.go @@ -0,0 +1,10 @@ +package server + +import ( + "github.com/Tnze/go-mc/net" + "github.com/google/uuid" +) + +type GamePlay interface { + AcceptPlayer(name string, id uuid.UUID, protocol int32, conn *net.Conn) +} diff --git a/server/handshake.go b/server/handshake.go new file mode 100644 index 0000000..2967a3c --- /dev/null +++ b/server/handshake.go @@ -0,0 +1,22 @@ +package server + +import ( + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" +) + +func (s *Server) handshake(conn *net.Conn) (protocol int32, intention int32, err error) { + var ( + Protocol, Intention pk.VarInt + ServerAddress pk.String // ignored + ServerPort pk.UnsignedShort // ignored + ) + // receive handshake packet + var p pk.Packet + err = conn.ReadPacket(&p) + if err != nil { + return 0, 0, err + } + err = p.Scan(&Protocol, &ServerAddress, &ServerPort, &Intention) + return int32(Protocol), int32(Intention), err +} diff --git a/server/login.go b/server/login.go new file mode 100644 index 0000000..b74213d --- /dev/null +++ b/server/login.go @@ -0,0 +1,80 @@ +package server + +import ( + "fmt" + + "github.com/Tnze/go-mc/data/packetid" + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/Tnze/go-mc/offline" + "github.com/Tnze/go-mc/server/auth" + "github.com/google/uuid" +) + +type LoginHandler interface { + AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, err error) +} + +type MojangLoginHandler struct { + OnlineMode bool + Threshold int +} + +func (d *MojangLoginHandler) AcceptLogin(conn *net.Conn, protocol int32) (name string, id uuid.UUID, err error) { + //login start + var p pk.Packet + err = conn.ReadPacket(&p) + if err != nil { + return + } + if p.ID != packetid.LoginStart { + err = wrongPacketErr{expect: packetid.LoginStart, get: p.ID} + return + } + + err = p.Scan((*pk.String)(&name)) //decode username as pk.String + if err != nil { + return + } + + //auth + if d.OnlineMode { + var resp *auth.Resp + //Auth, Encrypt + resp, err = auth.Encrypt(conn, name) + if err != nil { + return + } + name = resp.Name + id = resp.ID + } else { + // offline-mode UUID + id = offline.NameToUUID(name) + } + + //set compression + if d.Threshold >= 0 { + err = conn.WritePacket(pk.Marshal( + packetid.Compress, pk.VarInt(d.Threshold), + )) + if err != nil { + return + } + conn.SetThreshold(d.Threshold) + } + + // send login success + err = conn.WritePacket(pk.Marshal(packetid.Success, + pk.UUID(id), + pk.String(name), + )) + return +} + +type wrongPacketErr struct { + expect, get int32 +} + +func (w wrongPacketErr) Error() string { + return fmt.Sprintf("wrong packet id: expect %#02X, get %#02X", w.expect, w.get) +} diff --git a/server/ping.go b/server/ping.go new file mode 100644 index 0000000..4fd0837 --- /dev/null +++ b/server/ping.go @@ -0,0 +1,74 @@ +package server + +import ( + "encoding/json" + + "github.com/Tnze/go-mc/chat" + "github.com/Tnze/go-mc/net" + pk "github.com/Tnze/go-mc/net/packet" + "github.com/google/uuid" +) + +type ListPingHandler interface { + Name() string + Protocol() int + MaxPlayer() int + OnlinePlayer() int + PlayerSamples() []PlayerSample + Description() chat.Message +} + +type PlayerSample struct { + Name string `json:"name"` + ID uuid.UUID `json:"id"` +} + +func (s *Server) acceptListPing(conn *net.Conn) { + var p pk.Packet + for i := 0; i < 2; i++ { // Ping or List. Only allow check twice + err := conn.ReadPacket(&p) + if err != nil { + return + } + + switch p.ID { + case 0x00: //List + var resp []byte + resp, err = s.listResp() + if err != nil { + break + } + err = conn.WritePacket(pk.Marshal(0x00, pk.String(resp))) + case 0x01: //Ping + err = conn.WritePacket(p) + } + if err != nil { + return + } + } +} + +func (s *Server) listResp() ([]byte, error) { + var list struct { + Version struct { + Name string `json:"name"` + Protocol int `json:"protocol"` + } `json:"version"` + Players struct { + Max int `json:"max"` + Online int `json:"online"` + Sample []PlayerSample `json:"sample"` + } `json:"players"` + Description chat.Message `json:"description"` + FavIcon string `json:"favicon,omitempty"` + } + + list.Version.Name = s.ListPingHandler.Name() + list.Version.Protocol = s.ListPingHandler.Protocol() + list.Players.Max = s.ListPingHandler.MaxPlayer() + list.Players.Online = s.ListPingHandler.OnlinePlayer() + list.Players.Sample = s.ListPingHandler.PlayerSamples() + list.Description = s.ListPingHandler.Description() + + return json.Marshal(list) +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..79b1630 --- /dev/null +++ b/server/server.go @@ -0,0 +1,45 @@ +package server + +import "github.com/Tnze/go-mc/net" + +const ProtocolVersion = 756 + +type Server struct { + ListPingHandler + LoginHandler + GamePlay +} + +func (s *Server) Listen(addr string) error { + listener, err := net.ListenMC(addr) + if err != nil { + return err + } + + for { + conn, err := listener.Accept() + if err != nil { + return err + } + go s.acceptConn(&conn) + } +} + +func (s *Server) acceptConn(conn *net.Conn) { + defer conn.Close() + protocol, intention, err := s.handshake(conn) + if err != nil { + return + } + + switch intention { + case 1: // list ping + s.acceptListPing(conn) + case 2: // login + name, id, err := s.AcceptLogin(conn, protocol) + if err != nil { + return + } + s.AcceptPlayer(name, id, protocol, conn) + } +}