diff --git a/src/api/handlers.go b/src/api/handlers.go index 65d3d82c..5152739b 100644 --- a/src/api/handlers.go +++ b/src/api/handlers.go @@ -1,6 +1,7 @@ package api import ( + "encoding/base64" "encoding/json" "errors" "fmt" @@ -208,8 +209,16 @@ func RemoveSave(w http.ResponseWriter, r *http.Request) { func CreateSaveHandler(w http.ResponseWriter, r *http.Request) { var err error var resp interface{} + var mapGenSettingsFileName string + var mapSettingsFileName string defer func() { + if mapGenSettingsFileName != "" { + _ = os.Remove(mapGenSettingsFileName) + } + if mapSettingsFileName != "" { + _ = os.Remove(mapSettingsFileName) + } WriteResponse(w, resp) }() @@ -222,9 +231,47 @@ func CreateSaveHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) return } + + w.Header().Set("Content-Type", "application/json;charset=UTF-8") + + body, resp, err := ReadRequestBody(w, r) + if err != nil { + return + } + + var mapGenSettings factorio.MapGenSettings + mapGenSettings, resp, err = UnmarshallMapGenSettingsJson(body, w) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + mapGenSettingsFileName, resp, err = ParseSettingsAsFile(mapGenSettings) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + var mapSettings factorio.MapSettings + mapSettings, resp, err = UnmarshallMapSettingsJson(body, w) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + mapSettingsFileName, resp, err = ParseSettingsAsFile(mapSettings) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + config := bootstrap.GetConfig() saveFile := filepath.Join(config.FactorioSavesDir, saveName) - cmdOut, err := factorio.CreateSave(saveFile) + cmdOut, err := factorio.CreateSave(saveFile, mapGenSettingsFileName, mapSettingsFileName) if err != nil { resp = fmt.Sprintf("Error creating save {%s}: %s", saveName, err) log.Println(resp) @@ -410,6 +457,91 @@ func CheckServer(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json;charset=UTF-8") } +func DefaultMapSettings(w http.ResponseWriter, r *http.Request) { + WriteResponse(w, factorio.DefaultMapSettings()) +} + +func DefaultMapGenSettings(w http.ResponseWriter, r *http.Request) { + WriteResponse(w, factorio.DefaultMapGenSettings()) +} + +func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { + var resp interface{} + var mapGenSettingsFileName string + var mapSettingsFileName string + var previewImagePath string + + defer func() { + if mapGenSettingsFileName != "" { + _ = os.Remove(mapGenSettingsFileName) + } + if mapSettingsFileName != "" { + _ = os.Remove(mapSettingsFileName) + } + if previewImagePath != "" { + _ = os.Remove(previewImagePath) + } + WriteResponse(w, resp) + }() + + w.Header().Set("Content-Type", "application/json;charset=UTF-8") + + body, resp, err := ReadRequestBody(w, r) + if err != nil { + return + } + + var mapGenSettings factorio.MapGenSettings + mapGenSettings, resp, err = UnmarshallMapGenSettingsJson(body, w) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + mapGenSettingsFileName, resp, err = ParseSettingsAsFile(mapGenSettings) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + var mapSettings factorio.MapSettings + mapSettings, resp, err = UnmarshallMapSettingsJson(body, w) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + mapSettingsFileName, resp, err = ParseSettingsAsFile(mapSettings) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + previewImagePath, err = factorio.GenerateMapPreview(mapGenSettingsFileName, mapSettingsFileName) + + if err != nil { + resp = fmt.Sprintf("Error creating map preview %s", err) + log.Println(resp) + w.WriteHeader(http.StatusInternalServerError) + return + } + + previewImage, err := ioutil.ReadFile(previewImagePath) + + if err != nil { + resp = fmt.Sprintf("Error read preview image %s", err) + log.Println(resp) + w.WriteHeader(http.StatusInternalServerError) + return + } + + resp = "data:image/png;base64," + base64.StdEncoding.EncodeToString(previewImage) +} + func FactorioVersion(w http.ResponseWriter, r *http.Request) { resp := map[string]string{} @@ -435,6 +567,55 @@ func UnmarshallUserJson(body []byte, w http.ResponseWriter) (user User, resp int return } +func UnmarshallMapSettingsJson(body []byte, w http.ResponseWriter) (settings factorio.MapSettings, resp interface{}, err error) { + settings = factorio.DefaultMapSettings() + err = json.Unmarshal(body, &settings) + if err != nil { + resp = fmt.Sprintf("Unable to parse the request body: %s", err) + log.Println(resp) + w.WriteHeader(http.StatusBadRequest) + } + return +} + +func UnmarshallMapGenSettingsJson(body []byte, w http.ResponseWriter) (settings factorio.MapGenSettings, resp interface{}, err error) { + settings = factorio.DefaultMapGenSettings() + err = json.Unmarshal(body, &settings) + if err != nil { + resp = fmt.Sprintf("Unable to parse the request body: %s", err) + log.Println(resp) + w.WriteHeader(http.StatusBadRequest) + } + return +} + +func ParseSettingsAsFile(settings interface{}) (fileName string, resp interface{}, err error) { + + var fileContent []byte + fileContent, err = json.MarshalIndent(settings, "", "") + + if err != nil { + resp = fmt.Sprintf("Unable to parse the request body: %s", err) + return + } + + var file *os.File + file, err = ioutil.TempFile("", "factorio-settings-file") + + if err != nil { + resp = fmt.Sprint("Unable to create tmp file") + return + } + + _, err = file.Write(fileContent) + + if err != nil { + resp = fmt.Sprintf("Unable to write tmp file %s", file.Name()) + return + } + return file.Name(), resp, err +} + // Handler for the Login func LoginUser(w http.ResponseWriter, r *http.Request) { var err error diff --git a/src/api/routes.go b/src/api/routes.go index ceaf95ef..09373708 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -94,6 +94,10 @@ func NewRouter() *mux.Router { Methods("GET"). Name("Saves"). Handler(http.StripPrefix("/saves", http.FileServer(http.Dir("./app/")))) + subRouter.Path("/map-generator"). + Methods("GET"). + Name("MapGenerator"). + Handler(http.StripPrefix("/map-generator", http.FileServer(http.Dir("./app/")))) subRouter.Path("/mods"). Methods("GET"). Name("Mods"). @@ -161,10 +165,28 @@ var apiRoutes = Routes{ false, }, { "CreateSave", - "GET", + "POST", "/saves/create/{save}", CreateSaveHandler, true, + }, { + "GenerateMapPreview", + "POST", + "/saves/preview", + GenerateMapPreview, + true, + }, { + "DefaultMapSettings", + "GET", + "/saves/default-map-settings", + DefaultMapSettings, + false, + }, { + "DefaultMapGenSettings", + "GET", + "/saves/default-map-gen-settings", + DefaultMapGenSettings, + false, }, { "LoadModsFromSave", "POST", diff --git a/src/factorio/mapGenSettings.go b/src/factorio/mapGenSettings.go new file mode 100644 index 00000000..32dd8e6c --- /dev/null +++ b/src/factorio/mapGenSettings.go @@ -0,0 +1,128 @@ +package factorio + +type MapResource struct { + Frequency float32 `json:"frequency"` + Size float32 `json:"size"` + Richness float32 `json:"richness"` +} + +type AutoPlaceControls struct { + Coal MapResource `json:"coal"` + Stone MapResource `json:"stone"` + CopperOre MapResource `json:"copper-ore"` + IronOre MapResource `json:"iron-ore"` + UraniumOre MapResource `json:"uranium-ore"` + CrudeOil MapResource `json:"crude-oil"` + Trees MapResource `json:"trees"` + EnemyBase MapResource `json:"enemy-base"` +} + +type CliffSettings struct { + Name string `json:"name"` + CliffElevation0 int `json:"cliff_elevation_0"` + CliffElevationInterval int `json:"cliff_elevation_interval"` + Richness int `json:"richness"` +} + +type PropertyExpressionNames struct { + Elevation string `json:"elevation"` + MoistureFrequencyMultiplier string `json:"control-setting:moisture:frequency:multiplier"` + MoistureBias string `json:"control-setting:moisture:bias"` + AuxFrequencyMultiplier string `json:"control-setting:aux:frequency:multiplier"` + AuxBias string `json:"control-setting:aux:bias"` +} + +type StartingPoints []struct { + X int `json:"x"` + Y int `json:"y"` +} + +type MapGenSettings struct { + TerrainSegmentation int `json:"terrain_segmentation"` + Water int `json:"water"` + Width int `json:"width"` + Height int `json:"height"` + StartingArea float32 `json:"starting_area"` + PeacefulMode bool `json:"peaceful_mode"` + AutoPlaceControls AutoPlaceControls `json:"autoplace_controls"` + + CliffSettings CliffSettings `json:"cliff_settings"` + + PropertyExpressionNames PropertyExpressionNames `json:"property_expression_names"` + + StartingPoints StartingPoints `json:"starting_points"` + + Seed *int `json:"seed"` +} + +func DefaultMapGenSettings() MapGenSettings { + return MapGenSettings{ + TerrainSegmentation: 1, + Water: 1, + Width: 0, + Height: 0, + StartingArea: 1, + PeacefulMode: false, + AutoPlaceControls: AutoPlaceControls{ + Coal: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + Stone: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + CopperOre: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + IronOre: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + UraniumOre: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + CrudeOil: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + Trees: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + EnemyBase: MapResource{ + Frequency: 1, + Size: 1, + Richness: 1, + }, + }, + CliffSettings: CliffSettings{ + Name: string("cliff"), + CliffElevation0: 10, + CliffElevationInterval: 40, + Richness: 1, + }, + PropertyExpressionNames: PropertyExpressionNames{ + MoistureFrequencyMultiplier: "1", + MoistureBias: "0", + AuxFrequencyMultiplier: "1", + AuxBias: "0", + }, + StartingPoints: StartingPoints{ + { + X: 0, + Y: 0, + }, + }, + Seed: nil, + } +} diff --git a/src/factorio/mapSettings.go b/src/factorio/mapSettings.go new file mode 100644 index 00000000..45e53b59 --- /dev/null +++ b/src/factorio/mapSettings.go @@ -0,0 +1,234 @@ +package factorio + +type DifficultySettings struct { + RecipeDifficulty int `json:"recipe_difficulty"` + TechnologyDifficulty int `json:"technology_difficulty"` + TechnologyPriceMultiplier int `json:"technology_price_multiplier"` + ResearchQueueSetting string `json:"research_queue_setting"` +} + +type Pollution struct { + Enabled bool `json:"enabled"` + DiffusionRatio float32 `json:"diffusion_ratio"` + MinToDiffuse float32 `json:"min_to_diffuse"` + Ageing float32 `json:"ageing"` + ExpectedMaxPerChunk float32 `json:"expected_max_per_chunk"` + MinToShowPerChunk float32 `json:"min_to_show_per_chunk"` + MinPollutionToDamageTrees float32 `json:"min_pollution_to_damage_trees"` + PollutionWithMaxForestDamage float32 `json:"pollution_with_max_forest_damage"` + PollutionPerTreeDamage float32 `json:"pollution_per_tree_damage"` + PollutionRestoredPerTreeDamage float32 `json:"pollution_restored_per_tree_damage"` + MaxPollutionToRestoreTrees float32 `json:"max_pollution_to_restore_trees"` + EnemyAttackPollutionConsumptionModifier float32 `json:"enemy_attack_pollution_consumption_modifier"` +} + +type EnemyEvolution struct { + Enabled bool `json:"enabled"` + TimeFactor float32 `json:"time_factor"` + DestroyFactor float32 `json:"destroy_factor"` + PollutionFactor float32 `json:"pollution_factor"` +} + +type EnemyExpansion struct { + Enabled bool `json:"enabled"` + MinBaseSpacing int `json:"min_base_spacing"` + MaxExpansionDistance int `json:"max_expansion_distance"` + FriendlyBaseInfluenceRadius int `json:"friendly_base_influence_radius"` + EnemyBuildingInfluenceRadius int `json:"enemy_building_influence_radius"` + BuildingCoefficient float32 `json:"building_coefficient"` + OtherBaseCoefficient float32 `json:"other_base_coefficient"` + NeighbouringChunkCoefficient float32 `json:"neighbouring_chunk_coefficient"` + NeighbouringBaseChunkCoefficient float32 `json:"neighbouring_base_chunk_coefficient"` + MaxCollidingTilesCoefficient float32 `json:"max_colliding_tiles_coefficient"` + SettlerGroupMinSize int `json:"settler_group_min_size"` + SettlerGroupMaxSize int `json:"settler_group_max_size"` + MinExpansionCooldown int `json:"min_expansion_cooldown"` + MaxExpansionCooldown int `json:"max_expansion_cooldown"` +} + +type UnitGroup struct { + MinGroupGatheringTime int `json:"min_group_gathering_time"` + MaxGroupGatheringTime int `json:"max_group_gathering_time"` + MaxWaitTimeForLateMembers int `json:"max_wait_time_for_late_members"` + MaxGroupRadius float32 `json:"max_group_radius"` + MinGroupRadius float32 `json:"min_group_radius"` + MaxMemberSpeedupWhenBehind float32 `json:"max_member_speedup_when_behind"` + MaxMemberSlowdownWhenAhead float32 `json:"max_member_slowdown_when_ahead"` + MaxGroupSlowdownFactor float32 `json:"max_group_slowdown_factor"` + MaxGroupMemberFallbackFactor int `json:"max_group_member_fallback_factor"` + MemberDisownDistance int `json:"member_disown_distance"` + TickToleranceWhenMemberArrives int `json:"tick_tolerance_when_member_arrives"` + MaxGatheringUnitGroups int `json:"max_gathering_unit_groups"` + MaxUnitGroupSize int `json:"max_unit_group_size"` +} + +type SteeringConfig struct { + Radius float32 `json:"radius"` + SeparationForce float32 `json:"separation_force"` + SeparationFactor float32 `json:"separation_factor"` + ForceUnitFuzzyGotoBehavior bool `json:"force_unit_fuzzy_goto_behavior"` +} + +type Steering struct { + Default SteeringConfig `json:"default"` + Moving SteeringConfig `json:"moving"` +} + +type PathFinder struct { + Fwd2bwdRatio int `json:"fwd2bwd_ratio"` + GoalPressureRatio int `json:"goal_pressure_ratio"` + MaxStepsWorkedPerTick int `json:"max_steps_worked_per_tick"` + MaxWorkDonePerTick int `json:"max_work_done_per_tick"` + UsePathCache bool `json:"use_path_cache"` + ShortCacheSize int `json:"short_cache_size"` + LongCacheSize int `json:"long_cache_size"` + ShortCacheMinCacheableDistance int `json:"short_cache_min_cacheable_distance"` + ShortCacheMinAlgoStepsToCache int `json:"short_cache_min_algo_steps_to_cache"` + LongCacheMinCacheableDistance int `json:"long_cache_min_cacheable_distance"` + CacheMaxConnectToCacheStepsMultiplier int `json:"cache_max_connect_to_cache_steps_multiplier"` + CacheAcceptPathStartDistanceRatio float32 `json:"cache_accept_path_start_distance_ratio"` + CacheAcceptPathEndDistanceRatio float32 `json:"cache_accept_path_end_distance_ratio"` + NegativeCacheAcceptPathStartDistanceRatio float32 `json:"negative_cache_accept_path_start_distance_ratio"` + NegativeCacheAcceptPathEndDistanceRatio float32 `json:"negative_cache_accept_path_end_distance_ratio"` + CachePathStartDistanceRatingMultiplier int `json:"cache_path_start_distance_rating_multiplier"` + CachePathEndDistanceRatingMultiplier int `json:"cache_path_end_distance_rating_multiplier"` + StaleEnemyWithSameDestinationCollisionPenalty int `json:"stale_enemy_with_same_destination_collision_penalty"` + IgnoreMovingEnemyCollisionDistance int `json:"ignore_moving_enemy_collision_distance"` + EnemyWithDifferentDestinationCollisionPenalty int `json:"enemy_with_different_destination_collision_penalty"` + GeneralEntityCollisionPenalty int `json:"general_entity_collision_penalty"` + GeneralEntitySubsequentCollisionPenalty int `json:"general_entity_subsequent_collision_penalty"` + ExtendedCollisionPenalty int `json:"extended_collision_penalty"` + MaxClientsToAcceptAnyNewRequest int `json:"max_clients_to_accept_any_new_request"` + MaxClientsToAcceptShortNewRequest int `json:"max_clients_to_accept_short_new_request"` + DirectDistanceToConsiderShortRequest int `json:"direct_distance_to_consider_short_request"` + ShortRequestMaxSteps int `json:"short_request_max_steps"` + ShortRequestRatio float32 `json:"short_request_ratio"` + MinStepsToCheckPathFindTermination int `json:"min_steps_to_check_path_find_termination"` + StartToGoalCostMultiplierToTerminatePathFind float32 `json:"start_to_goal_cost_multiplier_to_terminate_path_find"` + OverloadLevels []int `json:"overload_levels"` + OverloadMultipliers []int `json:"overload_multipliers"` + NegativePathCacheDelayInterval int `json:"negative_path_cache_delay_interval"` +} + +type MapSettings struct { + DifficultySettings DifficultySettings `json:"difficulty_settings"` + Pollution Pollution `json:"pollution"` + EnemyEvolution EnemyEvolution `json:"enemy_evolution"` + EnemyExpansion EnemyExpansion `json:"enemy_expansion"` + UnitGroup UnitGroup `json:"unit_group"` + Steering Steering `json:"steering"` + PathFinder PathFinder `json:"path_finder"` + MaxFailedBehaviorCount int `json:"max_failed_behavior_count"` +} + +func DefaultMapSettings() MapSettings { + return MapSettings{ + DifficultySettings: DifficultySettings{ + RecipeDifficulty: 0, + TechnologyDifficulty: 0, + TechnologyPriceMultiplier: 1, + ResearchQueueSetting: "after-victory", + }, + Pollution: Pollution{ + Enabled: true, + DiffusionRatio: 0.02, + MinToDiffuse: 15, + Ageing: 1, + ExpectedMaxPerChunk: 150, + MinToShowPerChunk: 50, + MinPollutionToDamageTrees: 60, + PollutionWithMaxForestDamage: 150, + PollutionPerTreeDamage: 50, + PollutionRestoredPerTreeDamage: 10, + MaxPollutionToRestoreTrees: 20, + EnemyAttackPollutionConsumptionModifier: 1, + }, + EnemyEvolution: EnemyEvolution{ + Enabled: true, + TimeFactor: 0.000004, + DestroyFactor: 0.002, + PollutionFactor: 0.0000009, + }, + EnemyExpansion: EnemyExpansion{ + Enabled: true, + MinBaseSpacing: 3, + MaxExpansionDistance: 7, + FriendlyBaseInfluenceRadius: 2, + EnemyBuildingInfluenceRadius: 2, + BuildingCoefficient: 0.1, + OtherBaseCoefficient: 2.0, + NeighbouringChunkCoefficient: 0.5, + NeighbouringBaseChunkCoefficient: 0.4, + MaxCollidingTilesCoefficient: 0.9, + SettlerGroupMinSize: 5, + SettlerGroupMaxSize: 20, + MinExpansionCooldown: 14400, + MaxExpansionCooldown: 216000, + }, + UnitGroup: UnitGroup{ + MinGroupGatheringTime: 3600, + MaxGroupGatheringTime: 36000, + MaxWaitTimeForLateMembers: 7200, + MaxGroupRadius: 30.0, + MinGroupRadius: 5.0, + MaxMemberSpeedupWhenBehind: 1.4, + MaxMemberSlowdownWhenAhead: 0.6, + MaxGroupSlowdownFactor: 0.3, + MaxGroupMemberFallbackFactor: 3, + MemberDisownDistance: 10, + TickToleranceWhenMemberArrives: 60, + MaxGatheringUnitGroups: 30, + MaxUnitGroupSize: 200, + }, + Steering: Steering{ + Default: SteeringConfig{ + Radius: 1.2, + SeparationForce: 0.005, + SeparationFactor: 1.2, + ForceUnitFuzzyGotoBehavior: false, + }, + Moving: SteeringConfig{ + Radius: 3, + SeparationForce: 0.01, + SeparationFactor: 3, + ForceUnitFuzzyGotoBehavior: false, + }, + }, + PathFinder: PathFinder{ + Fwd2bwdRatio: 5, + GoalPressureRatio: 2, + MaxStepsWorkedPerTick: 100, + MaxWorkDonePerTick: 8000, + UsePathCache: true, + ShortCacheSize: 5, + LongCacheSize: 25, + ShortCacheMinCacheableDistance: 10, + ShortCacheMinAlgoStepsToCache: 50, + LongCacheMinCacheableDistance: 30, + CacheMaxConnectToCacheStepsMultiplier: 1000, + CacheAcceptPathStartDistanceRatio: 0.2, + CacheAcceptPathEndDistanceRatio: 0.15, + NegativeCacheAcceptPathStartDistanceRatio: 0.3, + NegativeCacheAcceptPathEndDistanceRatio: 0.3, + CachePathStartDistanceRatingMultiplier: 10, + CachePathEndDistanceRatingMultiplier: 20, + StaleEnemyWithSameDestinationCollisionPenalty: 30, + IgnoreMovingEnemyCollisionDistance: 5, + EnemyWithDifferentDestinationCollisionPenalty: 30, + GeneralEntityCollisionPenalty: 10, + GeneralEntitySubsequentCollisionPenalty: 3, + ExtendedCollisionPenalty: 3, + MaxClientsToAcceptAnyNewRequest: 10, + MaxClientsToAcceptShortNewRequest: 100, + DirectDistanceToConsiderShortRequest: 100, + ShortRequestMaxSteps: 1000, + ShortRequestRatio: 0.5, + MinStepsToCheckPathFindTermination: 2000, + StartToGoalCostMultiplierToTerminatePathFind: 500.0, + OverloadLevels: []int{0, 100, 500}, + OverloadMultipliers: []int{2, 3, 4}, + NegativePathCacheDelayInterval: 20, + }, + MaxFailedBehaviorCount: 3, + } +} diff --git a/src/factorio/saves.go b/src/factorio/saves.go index f4e31b2f..85970240 100644 --- a/src/factorio/saves.go +++ b/src/factorio/saves.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "github.com/OpenFactorioServerManager/factorio-server-manager/bootstrap" @@ -64,14 +65,21 @@ func (s *Save) Remove() error { } // Create savefiles for Factorio -func CreateSave(filePath string) (string, error) { +func CreateSave(filePath string, mapSettingsGenFilePath string, mapSettingsFilePath string) (string, error) { err := os.MkdirAll(filepath.Dir(filePath), 0755) if err != nil { log.Printf("Error in creating Factorio save: %s", err) return "", err } - args := []string{"--create", filePath} + args := []string{ + "--map-gen-settings", + mapSettingsGenFilePath, + "--map-settings", + mapSettingsFilePath, + "--create", + filePath, + } config := bootstrap.GetConfig() cmdOutput, err := exec.Command(config.FactorioBinary, args...).Output() if err != nil { @@ -84,3 +92,58 @@ func CreateSave(filePath string) (string, error) { return result, nil } + +func GenerateMapPreview(mapSettingsGenFilePath string, mapSettingsFilePath string) (string, error) { + + if _, err := os.Stat("./map_previews"); err != nil { + err = os.Mkdir("./map_previews", 664) + if err != nil { + log.Printf("Failed to create folder %s with permission: %s\n", "./map_previews", err) + return "", err + } + } + + previewFolder, _ := filepath.Abs("./map_previews") + + // adding slash will indicate for factorio it is a path and not an image file + // source: https://wiki.factorio.com/Command_line_parameters + previewFolder += string(filepath.Separator) + + args := []string{ + "--map-gen-settings", + mapSettingsGenFilePath, + "--generate-map-preview", + previewFolder, + "--map-settings", + mapSettingsFilePath, + } + + config := bootstrap.GetConfig() + log.Println(filepath.Abs(config.FactorioBinary)) + cmdOutput, err := exec.Command(config.FactorioBinary, args...).Output() + if err != nil { + log.Printf("Error in creating Factorio save: %s", err) + return "", err + } + + result := string(cmdOutput) + + // extract image path from result + startMark := "Wrote map preview image file: " + endMark := ".png" + + startIndex := strings.Index(result, startMark) + if startIndex == -1 { + return "", errors.New("failed to detect start of the image path") + } + startIndex += len(startMark) + endIndex := strings.Index(result[startIndex:], endMark) + + if endIndex == -1 { + return "", errors.New("failed to detect end of the image path") + } + endIndex += len(endMark) + imagePath := result[startIndex:][:endIndex] + + return imagePath, nil +} diff --git a/tailwind.config.js b/tailwind.config.js index 34d14ab8..25ab210d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -9,6 +9,9 @@ module.exports = { ], theme: { extend: { + minHeight: { + '1/2': '50%', + }, width: { 72: "18rem", 80: "20rem", diff --git a/ui/App/App.jsx b/ui/App/App.jsx index 9ed229f0..7063296a 100644 --- a/ui/App/App.jsx +++ b/ui/App/App.jsx @@ -1,4 +1,4 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useState, useEffect} from 'react'; import user from "../api/resources/user"; import Login from "./views/Login"; @@ -17,6 +17,7 @@ import Console from "./views/Console"; import Help from "./views/Help"; import socket from "../api/socket"; import {Flash} from "./components/Flash"; +import MapGenerator from "./views/MapGenerator/MapGenerator"; const App = () => { @@ -52,6 +53,12 @@ const App = () => { return ; } + useEffect(() => { + (async () => { + updateServerStatus() + })(); + }, []); + return ( @@ -62,6 +69,7 @@ const App = () => { }> }/> }/> + }/> }/> }/> }/> @@ -69,6 +77,7 @@ const App = () => { }/> }/> }/> + diff --git a/ui/App/components/Button.jsx b/ui/App/components/Button.jsx index 35cc0f48..3ad920fc 100644 --- a/ui/App/components/Button.jsx +++ b/ui/App/components/Button.jsx @@ -9,25 +9,28 @@ const Button = ({ children, type, onClick, isSubmit, className, size, isLoading, switch (type) { case 'success': - color = `bg-green ${isDisabled || isLoading ? null : "hover:glow-green hover:bg-green-light" }`; + color = `bg-green ${isDisabled || isLoading ? "" : "hover:glow-green hover:bg-green-light" }`; break; case 'danger': - color = `bg-red ${isDisabled || isLoading ? null : "hover:glow-red hover:bg-red-light"}`; + color = `bg-red ${isDisabled || isLoading ? "" : "hover:glow-red hover:bg-red-light"}`; break; default: - color = `bg-gray-light ${isDisabled || isLoading ? null : "hover:glow-orange hover:bg-orange"}` + color = `bg-gray-light ${isDisabled || isLoading ? "" : "hover:glow-orange hover:bg-orange"}` } switch (size) { case 'sm': padding = 'py-1 px-2'; break; + case 'none': + padding = ""; + break; default: padding = 'py-2 px-4' } return ( - diff --git a/ui/App/components/Checkbox.jsx b/ui/App/components/Checkbox.jsx index e65355cf..1142826f 100644 --- a/ui/App/components/Checkbox.jsx +++ b/ui/App/components/Checkbox.jsx @@ -1,13 +1,35 @@ -import React from "react"; +import React, {useState, useEffect} from "react"; + +const Checkbox = ({name, text, register = {}, checked, className, textSize = 'sm', onChange = null}) => { + + const [value, setValue] = useState(false); + + const updateChecked = event => { + if(onChange) { + event.persist(); + onChange(event); + } + setValue(event.target.checked) + } + + useEffect(() => { + if (typeof checked === 'boolean') { + setValue(checked); + } + }, [checked]); -const Checkbox = ({text, register, checked}) => { return ( -