diff --git a/Presets/Large City.json b/Presets/Large City.json new file mode 100644 index 0000000..80759d5 --- /dev/null +++ b/Presets/Large City.json @@ -0,0 +1,54 @@ +{ + "detail": 11, + "roughness": 25, + "width": 4000, + "height": 4000, + "lakes": 0, + "lake_size_lower": 15, + "lake_size_upper": 25, + "lake_edge_roughness": 40, + "lake_shape": "procedural", + "rivers": 0, + "min_river_width": 5, + "max_river_width": 10, + "river_curvyness": 51, + "river_width_variability": 50, + "river_edge_roughness": 6, + "min_tree_size": 0.4, + "max_tree_size": 1, + "tree_coverage": 5, + "tree_clumpiness": 99, + "min_road_width": 0.2, + "max_road_width": 0.6, + "buildings_per_road": 5, + "road_exits": 1, + "road_curvyness": 45, + "road_distribution": 70, + "min_road_angle": 30, + "min_wall_width": 0.5, + "max_wall_width": 1.5, + "num_walls": 0, + "city_coverage": 70, + "wall_curvyness": 35, + "show_turrets": true, + "turret_size": 2.5000000000000004, + "turret_shape": "circular", + "turret_spacing": 20, + "gate_spacing": 34, + "num_buildings": 1500, + "min_building_size": 1, + "max_building_size": 2.5, + "building_distribution": 0, + "building_shape": "procedural", + "building_shape_ratios": { + "circles": 3.97509765625, + "rectangles": 87, + "squares": 9.02490234375 + }, + "min_building_complexity": 2, + "max_building_complexity": 3, + "building_complexity_ratio": 50, + "seed": 1773254459149268515, + "last_export_path": "", + "image_view_state": 0 +} \ No newline at end of file diff --git a/Presets/Medium Town.json b/Presets/Medium Town.json new file mode 100644 index 0000000..0e5ffc8 --- /dev/null +++ b/Presets/Medium Town.json @@ -0,0 +1,54 @@ +{ + "detail": 11, + "roughness": 25, + "width": 4000, + "height": 4000, + "lakes": 0, + "lake_size_lower": 15, + "lake_size_upper": 25, + "lake_edge_roughness": 40, + "lake_shape": "procedural", + "rivers": 0, + "min_river_width": 5, + "max_river_width": 10, + "river_curvyness": 51, + "river_width_variability": 50, + "river_edge_roughness": 6, + "min_tree_size": 0.4, + "max_tree_size": 1, + "tree_coverage": 30, + "tree_clumpiness": 99, + "min_road_width": 0.2, + "max_road_width": 0.6, + "buildings_per_road": 3, + "road_exits": 1, + "road_curvyness": 45, + "road_distribution": 40, + "min_road_angle": 30, + "min_wall_width": 0.5, + "max_wall_width": 1.5, + "num_walls": 0, + "city_coverage": 70, + "wall_curvyness": 35, + "show_turrets": true, + "turret_size": 2.5000000000000004, + "turret_shape": "circular", + "turret_spacing": 20, + "gate_spacing": 34, + "num_buildings": 50, + "min_building_size": 2.5, + "max_building_size": 5, + "building_distribution": 0, + "building_shape": "procedural", + "building_shape_ratios": { + "circles": 3.97509765625, + "rectangles": 87, + "squares": 9.02490234375 + }, + "min_building_complexity": 2, + "max_building_complexity": 3, + "building_complexity_ratio": 50, + "seed": 1773254246832144883, + "last_export_path": "", + "image_view_state": 0 +} \ No newline at end of file diff --git a/Presets/Metropolis.json b/Presets/Metropolis.json new file mode 100644 index 0000000..319f742 --- /dev/null +++ b/Presets/Metropolis.json @@ -0,0 +1,54 @@ +{ + "detail": 11, + "roughness": 25, + "width": 4000, + "height": 4000, + "lakes": 0, + "lake_size_lower": 15, + "lake_size_upper": 25, + "lake_edge_roughness": 40, + "lake_shape": "procedural", + "rivers": 0, + "min_river_width": 5, + "max_river_width": 10, + "river_curvyness": 51, + "river_width_variability": 50, + "river_edge_roughness": 6, + "min_tree_size": 0.4, + "max_tree_size": 1, + "tree_coverage": 5, + "tree_clumpiness": 99, + "min_road_width": 0.2, + "max_road_width": 0.30000000000000004, + "buildings_per_road": 6, + "road_exits": 3, + "road_curvyness": 45, + "road_distribution": 70, + "min_road_angle": 30, + "min_wall_width": 0.5, + "max_wall_width": 1.5, + "num_walls": 0, + "city_coverage": 70, + "wall_curvyness": 35, + "show_turrets": true, + "turret_size": 2.5000000000000004, + "turret_shape": "circular", + "turret_spacing": 20, + "gate_spacing": 34, + "num_buildings": 7000, + "min_building_size": 0.5, + "max_building_size": 1, + "building_distribution": 0, + "building_shape": "procedural", + "building_shape_ratios": { + "circles": 3.97509765625, + "rectangles": 87, + "squares": 9.02490234375 + }, + "min_building_complexity": 2, + "max_building_complexity": 3, + "building_complexity_ratio": 50, + "seed": 1773254695515253606, + "last_export_path": "", + "image_view_state": 0 +} \ No newline at end of file diff --git a/Presets/Small City.json b/Presets/Small City.json new file mode 100644 index 0000000..aa0d81b --- /dev/null +++ b/Presets/Small City.json @@ -0,0 +1,54 @@ +{ + "detail": 11, + "roughness": 25, + "width": 4000, + "height": 4000, + "lakes": 0, + "lake_size_lower": 15, + "lake_size_upper": 25, + "lake_edge_roughness": 40, + "lake_shape": "procedural", + "rivers": 0, + "min_river_width": 5, + "max_river_width": 10, + "river_curvyness": 51, + "river_width_variability": 50, + "river_edge_roughness": 6, + "min_tree_size": 0.4, + "max_tree_size": 1, + "tree_coverage": 10, + "tree_clumpiness": 99, + "min_road_width": 0.2, + "max_road_width": 0.6, + "buildings_per_road": 5, + "road_exits": 1, + "road_curvyness": 45, + "road_distribution": 70, + "min_road_angle": 30, + "min_wall_width": 0.5, + "max_wall_width": 1.5, + "num_walls": 0, + "city_coverage": 70, + "wall_curvyness": 35, + "show_turrets": true, + "turret_size": 2.5000000000000004, + "turret_shape": "circular", + "turret_spacing": 20, + "gate_spacing": 34, + "num_buildings": 500, + "min_building_size": 1.5, + "max_building_size": 3.5, + "building_distribution": 0, + "building_shape": "procedural", + "building_shape_ratios": { + "circles": 3.97509765625, + "rectangles": 87, + "squares": 9.02490234375 + }, + "min_building_complexity": 2, + "max_building_complexity": 3, + "building_complexity_ratio": 50, + "seed": 1773254407932680563, + "last_export_path": "", + "image_view_state": 0 +} \ No newline at end of file diff --git a/Presets/Small Town.json b/Presets/Small Town.json new file mode 100644 index 0000000..687d78a --- /dev/null +++ b/Presets/Small Town.json @@ -0,0 +1,54 @@ +{ + "detail": 11, + "roughness": 25, + "width": 4000, + "height": 4000, + "lakes": 0, + "lake_size_lower": 15, + "lake_size_upper": 25, + "lake_edge_roughness": 40, + "lake_shape": "procedural", + "rivers": 0, + "min_river_width": 5, + "max_river_width": 10, + "river_curvyness": 51, + "river_width_variability": 50, + "river_edge_roughness": 6, + "min_tree_size": 0.4, + "max_tree_size": 1, + "tree_coverage": 30, + "tree_clumpiness": 99, + "min_road_width": 0.2, + "max_road_width": 0.6, + "buildings_per_road": 6, + "road_exits": 1, + "road_curvyness": 45, + "road_distribution": 19, + "min_road_angle": 30, + "min_wall_width": 0.5, + "max_wall_width": 1.5, + "num_walls": 0, + "city_coverage": 70, + "wall_curvyness": 35, + "show_turrets": true, + "turret_size": 2.5000000000000004, + "turret_shape": "circular", + "turret_spacing": 20, + "gate_spacing": 34, + "num_buildings": 10, + "min_building_size": 2.5, + "max_building_size": 5, + "building_distribution": 0, + "building_shape": "procedural", + "building_shape_ratios": { + "circles": 3.97509765625, + "rectangles": 87, + "squares": 9.02490234375 + }, + "min_building_complexity": 2, + "max_building_complexity": 3, + "building_complexity_ratio": 50, + "seed": 1773254133615935858, + "last_export_path": "", + "image_view_state": 0 +} \ No newline at end of file diff --git a/main.go b/main.go index d075930..1518769 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "archive/zip" "bytes" "compress/gzip" + "embed" "encoding/json" "fmt" "image" @@ -13,9 +14,11 @@ import ( "image/jpeg" "image/png" "io" + "io/fs" "log" "os" "path/filepath" + "sort" "strconv" "strings" "time" @@ -33,6 +36,9 @@ import ( const generationStepTimeout = time.Minute +//go:embed Presets/*.json +var embeddedPresets embed.FS + func runWithTimeout[T any](timeout time.Duration, fn func() T) (T, bool) { ch := make(chan T, 1) go func() { @@ -61,6 +67,45 @@ func cloneToRGBA(src image.Image, width, height int) *image.RGBA { return out } +type presetEntry struct { + Name string + Path string +} + +func listEmbeddedPresets() ([]presetEntry, error) { + entries, err := fs.ReadDir(embeddedPresets, "Presets") + if err != nil { + return nil, err + } + var presets []presetEntry + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + if !strings.HasSuffix(strings.ToLower(name), ".json") { + continue + } + displayName := strings.TrimSuffix(name, filepath.Ext(name)) + presets = append(presets, presetEntry{ + Name: displayName, + Path: filepath.Join("Presets", name), + }) + } + sort.Slice(presets, func(i, j int) bool { + return presets[i].Name < presets[j].Name + }) + return presets, nil +} + +func loadEmbeddedPreset(path string) (*Settings, error) { + raw, err := embeddedPresets.ReadFile(path) + if err != nil { + return nil, err + } + return decodeSettingsJSON(raw) +} + func main() { // Initialize the Fyne application and window a := app.NewWithID("com.example.rpgcitymaker") @@ -1549,6 +1594,34 @@ func main() { updateGenerateBtnState() } + presetEntries, err := listEmbeddedPresets() + if err != nil { + log.Println("Error loading embedded presets:", err) + } + presetNames := []string{"No Preset"} + presetPathByName := map[string]string{} + for _, preset := range presetEntries { + presetNames = append(presetNames, preset.Name) + presetPathByName[preset.Name] = preset.Path + } + presetSelect := widget.NewSelect(presetNames, func(name string) { + if name == "" || name == "No Preset" { + return + } + path, ok := presetPathByName[name] + if !ok { + return + } + imported, err := loadEmbeddedPreset(path) + if err != nil { + dialog.ShowError(err, w) + return + } + imported.Seed = settings.Seed + applySettingsToUI(imported) + }) + presetSelect.SetSelected("No Preset") + buildingsTab := container.NewTabItem("Buildings", container.NewVBox( numBuildingsSlider, minBuildingSizeSlider, @@ -1561,6 +1634,9 @@ func main() { )) imageTab := container.NewTabItem("Image", container.NewVBox( + widget.NewLabel("Preset:"), + presetSelect, + widget.NewSeparator(), widget.NewLabel("Width:"), widthEntry, widget.NewLabel("Height:"), heightEntry,