From 2996a392438671c834ccb4fc9606a4c6d463803f Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Sun, 28 Feb 2021 12:45:47 +0100 Subject: [PATCH 01/13] ui structure for map generator --- src/api/routes.go | 4 ++ ui/App/App.jsx | 2 + ui/App/components/Button.jsx | 3 + ui/App/components/Input.jsx | 4 +- ui/App/components/Layout.jsx | 1 + ui/App/components/Panel.jsx | 5 +- ui/App/components/Select.jsx | 36 +++++----- ui/App/components/Slider.jsx | 7 ++ ui/App/components/Tabs/TabControl.jsx | 14 ++-- ui/App/views/MapGenerator/MapGenerator.jsx | 45 ++++++++++++ .../MapGenerator/components/MapTypeSelect.jsx | 69 +++++++++++++++++++ .../MapGenerator/components/SeedInput.jsx | 33 +++++++++ ui/App/views/MapGenerator/tabs/Advanced.jsx | 7 ++ ui/App/views/MapGenerator/tabs/Enemy.jsx | 7 ++ ui/App/views/MapGenerator/tabs/Terrain.jsx | 7 ++ .../MapGenerator/tabs/resources/Resources.jsx | 24 +++++++ .../components/ResourceConfigurator.jsx | 27 ++++++++ .../views/Saves/components/UploadSaveForm.jsx | 11 ++- ui/index.scss | 25 +++++++ 19 files changed, 297 insertions(+), 34 deletions(-) create mode 100644 ui/App/components/Slider.jsx create mode 100644 ui/App/views/MapGenerator/MapGenerator.jsx create mode 100644 ui/App/views/MapGenerator/components/MapTypeSelect.jsx create mode 100644 ui/App/views/MapGenerator/components/SeedInput.jsx create mode 100644 ui/App/views/MapGenerator/tabs/Advanced.jsx create mode 100644 ui/App/views/MapGenerator/tabs/Enemy.jsx create mode 100644 ui/App/views/MapGenerator/tabs/Terrain.jsx create mode 100644 ui/App/views/MapGenerator/tabs/resources/Resources.jsx create mode 100644 ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx diff --git a/src/api/routes.go b/src/api/routes.go index 0dbf0216..5ac3a122 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/")))) + sr.Path("/map-generator"). + Methods("GET"). + Name("MapGenerator"). + Handler(http.StripPrefix("/map-generator", http.FileServer(http.Dir("./app/")))) sr.Path("/mods"). Methods("GET"). Name("Mods"). diff --git a/ui/App/App.jsx b/ui/App/App.jsx index 9c1f0b0f..2fa842c7 100644 --- a/ui/App/App.jsx +++ b/ui/App/App.jsx @@ -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 = () => { @@ -67,6 +68,7 @@ const App = () => { + diff --git a/ui/App/components/Button.jsx b/ui/App/components/Button.jsx index 35cc0f48..586d09bd 100644 --- a/ui/App/components/Button.jsx +++ b/ui/App/components/Button.jsx @@ -22,6 +22,9 @@ const Button = ({ children, type, onClick, isSubmit, className, size, isLoading, case 'sm': padding = 'py-1 px-2'; break; + case 'none': + padding = ''; + break; default: padding = 'py-2 px-4' } diff --git a/ui/App/components/Input.jsx b/ui/App/components/Input.jsx index 998df12d..4b5919dc 100644 --- a/ui/App/components/Input.jsx +++ b/ui/App/components/Input.jsx @@ -8,19 +8,21 @@ const Input = ({ defaultValue = null, hasAutoComplete = true, onKeyDown = () => null, + onChange = () => null, min = null, value = undefined, disabled = false }) => { return ( {
Controls Saves + Map Generator Mods Server Settings Game Settings diff --git a/ui/App/components/Panel.jsx b/ui/App/components/Panel.jsx index e0707cfd..26920529 100644 --- a/ui/App/components/Panel.jsx +++ b/ui/App/components/Panel.jsx @@ -9,11 +9,10 @@ const Panel = ({title, content, actions, className}) => {
{content}
- {actions - ?
+ {actions && +
{actions}
- : null }
) diff --git a/ui/App/components/Select.jsx b/ui/App/components/Select.jsx index 7b3148fa..fd3af9cc 100644 --- a/ui/App/components/Select.jsx +++ b/ui/App/components/Select.jsx @@ -6,24 +6,24 @@ const Select = ({name, inputRef, options, className = "", defaultValue = ""}) => return (
- -
- - - -
+ +
+ + + +
) } diff --git a/ui/App/components/Slider.jsx b/ui/App/components/Slider.jsx new file mode 100644 index 00000000..cf810893 --- /dev/null +++ b/ui/App/components/Slider.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Slider = () => { + return +} + +export default Slider; \ No newline at end of file diff --git a/ui/App/components/Tabs/TabControl.jsx b/ui/App/components/Tabs/TabControl.jsx index a2a7d94e..2235a70a 100644 --- a/ui/App/components/Tabs/TabControl.jsx +++ b/ui/App/components/Tabs/TabControl.jsx @@ -1,7 +1,8 @@ import React, {useState} from "react"; import TabTitle from "./TabTitle"; +import Panel from "../Panel"; -const TabControl = ({children}) => { +const TabControl = ({children, actions = null, title= null}) => { const [selectedTab, setSelectedTab] = useState(0) return ( @@ -17,11 +18,12 @@ const TabControl = ({children}) => { /> ))}
-
-
- {children[selectedTab]} -
-
+ ) } diff --git a/ui/App/views/MapGenerator/MapGenerator.jsx b/ui/App/views/MapGenerator/MapGenerator.jsx new file mode 100644 index 00000000..62527ed5 --- /dev/null +++ b/ui/App/views/MapGenerator/MapGenerator.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import TabControl from "../../components/Tabs/TabControl"; +import Tab from "../../components/Tabs/Tab"; +import Button from "../../components/Button"; +import Resources from "./tabs/resources/Resources"; +import Terrain from "./tabs/Terrain"; +import Enemy from "./tabs/Enemy"; +import Advanced from "./tabs/Advanced"; +import {useForm} from "react-hook-form"; +import SeedInput from "./components/SeedInput"; +import MapTypeSelect from "./components/MapTypeSelect"; + +const MapGenerator = () => { + + const {register, handleSubmit} = useForm() + + return
null)}> + Generate Map + } + title={ +
+ + +
+ } + > + + + + + + + + + + + + +
+
+} + +export default MapGenerator; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/components/MapTypeSelect.jsx b/ui/App/views/MapGenerator/components/MapTypeSelect.jsx new file mode 100644 index 00000000..fe4737d1 --- /dev/null +++ b/ui/App/views/MapGenerator/components/MapTypeSelect.jsx @@ -0,0 +1,69 @@ +import React, {useState} from "react"; + +const MapTypeSelect = ({inputRef, onChange}) => { + + const [value, setValue] = useState("default"); + + const options = [ + { + name: "Default", + value: "default" + }, + { + name: "Rich resources", + value: "rich-resources" + }, + { + name: "Marathon", + value: "marathon" + }, + { + name: "Death world", + value: "death-world" + }, + { + name: "Death world marathon", + value: "death-world-marathon" + }, + { + name: "Rail world", + value: "rail-world" + }, + { + name: "Ribbon world", + value: "ribbon-world" + }, + { + name: "Island", + value: "island" + }, + ]; + + const change = optionElement => { + setValue(optionElement.target.value) + // onChange(optionElement) + } + + return
+ +
+ + + +
+
+} + +export default MapTypeSelect; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/components/SeedInput.jsx b/ui/App/views/MapGenerator/components/SeedInput.jsx new file mode 100644 index 00000000..640b6965 --- /dev/null +++ b/ui/App/views/MapGenerator/components/SeedInput.jsx @@ -0,0 +1,33 @@ +import React, {useState, useEffect} from "react"; +import Input from "../../../components/Input"; +import Button from "../../../components/Button"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faRandom} from "@fortawesome/free-solid-svg-icons"; + + + +const SeedInput = ({inputRef}) => { + + const [seed, setSeed] = useState(0); + + const randomSeed = () => { + setSeed(Math.floor(Math.random() * 1000000000)) + } + useEffect(randomSeed, []); + + return
+
+ +
+ + +
+} + +export default SeedInput; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Advanced.jsx b/ui/App/views/MapGenerator/tabs/Advanced.jsx new file mode 100644 index 00000000..a56ed195 --- /dev/null +++ b/ui/App/views/MapGenerator/tabs/Advanced.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Advanced = () => { + return <> +} + +export default Advanced; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Enemy.jsx b/ui/App/views/MapGenerator/tabs/Enemy.jsx new file mode 100644 index 00000000..c9a4e738 --- /dev/null +++ b/ui/App/views/MapGenerator/tabs/Enemy.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Enemy = () => { + return <> +} + +export default Enemy; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Terrain.jsx b/ui/App/views/MapGenerator/tabs/Terrain.jsx new file mode 100644 index 00000000..7e122673 --- /dev/null +++ b/ui/App/views/MapGenerator/tabs/Terrain.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Terrain = () => { + return <> +} + +export default Terrain; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx new file mode 100644 index 00000000..a3819dba --- /dev/null +++ b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import ResourceConfigurator from "./components/ResourceConfigurator"; + +const Resources = () => { + return + + + + + + + + + + + + + + + +
NameFrequencySizeRichness
+} + +export default Resources; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx new file mode 100644 index 00000000..821890c7 --- /dev/null +++ b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx @@ -0,0 +1,27 @@ +import React from "react"; +import Slider from "../../../../../components/Slider"; + +const ResourceConfigurator = ({ name }) => { + return + + + + + {name} + + + + + + + + + + + +} + +export default ResourceConfigurator; \ No newline at end of file diff --git a/ui/App/views/Saves/components/UploadSaveForm.jsx b/ui/App/views/Saves/components/UploadSaveForm.jsx index 034be606..6fe57c02 100644 --- a/ui/App/views/Saves/components/UploadSaveForm.jsx +++ b/ui/App/views/Saves/components/UploadSaveForm.jsx @@ -3,6 +3,7 @@ import React, {useState} from "react"; import {useForm} from "react-hook-form"; import saves from "../../../../api/resources/saves"; import Error from "../../../components/Error"; +import Label from "../../../components/Label"; const UploadSaveForm = ({onSuccess}) => { @@ -19,17 +20,15 @@ const UploadSaveForm = ({onSuccess}) => { return (
- -
+
diff --git a/ui/index.scss b/ui/index.scss index 15e88ecb..5ec8f736 100644 --- a/ui/index.scss +++ b/ui/index.scss @@ -40,4 +40,29 @@ .hover\:glow-red:hover { box-shadow: 0 0 3px 1px rgba(254, 90, 90, 0.8); +} + +/* The slider itself */ +.slider { + -webkit-appearance: none; /* Override default CSS styles */ + appearance: none; + width: 100%; /* Full-width */ + height: 2px; /* Specified height */ + outline: none; /* Remove outline */ + opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */ + -webkit-transition: .2s; /* 0.2 seconds transition on hover */ + transition: opacity .2s; + @apply .bg-black + +} + +/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ +.slider::-webkit-slider-thumb { + -webkit-appearance: none; /* Override default look */ + appearance: none; + @apply .bg-gray-light .cursor-pointer .h-2 +} + +.slider::-moz-range-thumb { + @apply .bg-gray-light .cursor-pointer .h-2 } \ No newline at end of file From 56b7d5e5c041453d73a37e3a8a5d7901d2259143 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Sat, 6 Mar 2021 23:47:55 +0100 Subject: [PATCH 02/13] add endpoint to create a random map preview and return the filepath --- src/api/handlers.go | 22 +++++++++++++++++++ src/api/routes.go | 6 ++++++ src/factorio/saves.go | 49 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/api/handlers.go b/src/api/handlers.go index afc02143..f1e288b1 100644 --- a/src/api/handlers.go +++ b/src/api/handlers.go @@ -421,6 +421,28 @@ func CheckServer(w http.ResponseWriter, r *http.Request) { } } +func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { + var resp interface{} + + defer func() { + WriteResponse(w, resp) + }() + + // todo: evaluate post params to create a custom map-gen-setting file + p := "./map-gen-settings.json.example" + + previewImagePath, err := factorio.GenerateMapPreview(p) + + if err != nil { + resp = fmt.Sprintf("Error creating map preview %s", err) + log.Println(resp) + w.WriteHeader(http.StatusInternalServerError) + return + } + + resp = previewImagePath +} + func FactorioVersion(w http.ResponseWriter, r *http.Request) { resp := map[string]string{} diff --git a/src/api/routes.go b/src/api/routes.go index 5ac3a122..4174754f 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -169,6 +169,12 @@ var apiRoutes = Routes{ "/saves/create/{save}", CreateSaveHandler, true, + }, { + "GenerateMapPreview", + "GET", + "/saves/preview", + GenerateMapPreview, + true, }, { "LoadModsFromSave", "POST", diff --git a/src/factorio/saves.go b/src/factorio/saves.go index 01051b87..4797d411 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" @@ -83,3 +84,51 @@ func CreateSave(filePath string) (string, error) { return result, nil } + +func GenerateMapPreview(mapSettingsPath 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", mapSettingsPath, "--generate-map-preview", previewFolder} + + 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 +} From 6c036e159e22217c8140293bba48e0787dc52ff7 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Wed, 10 Mar 2021 23:01:23 +0100 Subject: [PATCH 03/13] generation preview endpoint now accepts map gen settings --- src/api/handlers.go | 46 +++++++++++- src/api/routes.go | 2 +- src/factorio/mapGenSettings.go | 127 +++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 src/factorio/mapGenSettings.go diff --git a/src/api/handlers.go b/src/api/handlers.go index f1e288b1..8cc9e285 100644 --- a/src/api/handlers.go +++ b/src/api/handlers.go @@ -423,15 +423,44 @@ func CheckServer(w http.ResponseWriter, r *http.Request) { func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { var resp interface{} + var mapGenSettingsFileName string defer func() { + if mapGenSettingsFileName != "" { + _ = os.Remove(mapGenSettingsFileName) + } WriteResponse(w, resp) }() - // todo: evaluate post params to create a custom map-gen-setting file - p := "./map-gen-settings.json.example" + w.Header().Set("Content-Type", "application/json;charset=UTF-8") + + body, resp, err := ReadRequestBody(w, r) + if err != nil { + return + } + + mapGenSettings, resp, err := UnmarshallMapGenSettingsJson(body, w) + if err != nil { + return + } + + mapGenSettingsFileContent, err := json.MarshalIndent(mapGenSettings, "", "") - previewImagePath, err := factorio.GenerateMapPreview(p) + mapGenSettingsFile, err := ioutil.TempFile("", "factorio-map-gen-settings") + + if err != nil { + return + } + + mapGenSettingsFileName = mapGenSettingsFile.Name() + + _, err = mapGenSettingsFile.Write(mapGenSettingsFileContent) + + if err != nil { + return + } + + previewImagePath, err := factorio.GenerateMapPreview(mapGenSettingsFileName) if err != nil { resp = fmt.Sprintf("Error creating map preview %s", err) @@ -468,6 +497,17 @@ func UnmarshallUserJson(body []byte, w http.ResponseWriter) (user User, resp int return } +func UnmarshallMapGenSettingsJson(body []byte, w http.ResponseWriter) (mapGenSettings factorio.MapGenSettings, resp interface{}, err error) { + mapGenSettings = factorio.DefaultMapGenSettings() + err = json.Unmarshal(body, &mapGenSettings) + if err != nil { + resp = fmt.Sprintf("Unable to parse the request body: %s", err) + log.Println(resp) + w.WriteHeader(http.StatusBadRequest) + } + return +} + // 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 4174754f..b973ecb1 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -171,7 +171,7 @@ var apiRoutes = Routes{ true, }, { "GenerateMapPreview", - "GET", + "POST", "/saves/preview", GenerateMapPreview, true, diff --git a/src/factorio/mapGenSettings.go b/src/factorio/mapGenSettings.go new file mode 100644 index 00000000..63946f57 --- /dev/null +++ b/src/factorio/mapGenSettings.go @@ -0,0 +1,127 @@ +package factorio + +type MapResource struct { + Frequency int `json:"frequency"` + Size int `json:"size"` + Richness int `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 { + 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 int `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, + } +} From 78f7da617d404a04679a0c8f691216c9d95c995f Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Fri, 12 Mar 2021 10:46:40 +0100 Subject: [PATCH 04/13] generation preview endpoint now accepts map settings --- src/api/handlers.go | 72 +++++++++-- src/factorio/mapSettings.go | 234 ++++++++++++++++++++++++++++++++++++ src/factorio/saves.go | 11 +- 3 files changed, 305 insertions(+), 12 deletions(-) create mode 100644 src/factorio/mapSettings.go diff --git a/src/api/handlers.go b/src/api/handlers.go index 8cc9e285..703a3bcd 100644 --- a/src/api/handlers.go +++ b/src/api/handlers.go @@ -424,11 +424,15 @@ func CheckServer(w http.ResponseWriter, r *http.Request) { func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { var resp interface{} var mapGenSettingsFileName string + var mapSettingsFileName string defer func() { if mapGenSettingsFileName != "" { _ = os.Remove(mapGenSettingsFileName) } + if mapSettingsFileName != "" { + _ = os.Remove(mapSettingsFileName) + } WriteResponse(w, resp) }() @@ -439,28 +443,38 @@ func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { return } - mapGenSettings, resp, err := UnmarshallMapGenSettingsJson(body, w) + var mapGenSettings factorio.MapGenSettings + mapGenSettings, resp, err = UnmarshallMapGenSettingsJson(body, w) + if err != nil { + w.WriteHeader(http.StatusBadRequest) return } - mapGenSettingsFileContent, err := json.MarshalIndent(mapGenSettings, "", "") - - mapGenSettingsFile, err := ioutil.TempFile("", "factorio-map-gen-settings") + mapGenSettingsFileName, resp, err = ParseSettingsAsFile(mapGenSettings) if err != nil { + w.WriteHeader(http.StatusBadRequest) return } - mapGenSettingsFileName = mapGenSettingsFile.Name() + var mapSettings factorio.MapSettings + mapSettings, resp, err = UnmarshallMapSettingsJson(body, w) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } - _, err = mapGenSettingsFile.Write(mapGenSettingsFileContent) + mapSettingsFileName, resp, err = ParseSettingsAsFile(mapSettings) if err != nil { + w.WriteHeader(http.StatusBadRequest) return } - previewImagePath, err := factorio.GenerateMapPreview(mapGenSettingsFileName) + var previewImagePath string + previewImagePath, err = factorio.GenerateMapPreview(mapGenSettingsFileName, mapSettingsFileName) if err != nil { resp = fmt.Sprintf("Error creating map preview %s", err) @@ -497,9 +511,20 @@ func UnmarshallUserJson(body []byte, w http.ResponseWriter) (user User, resp int return } -func UnmarshallMapGenSettingsJson(body []byte, w http.ResponseWriter) (mapGenSettings factorio.MapGenSettings, resp interface{}, err error) { - mapGenSettings = factorio.DefaultMapGenSettings() - err = json.Unmarshal(body, &mapGenSettings) +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) @@ -508,6 +533,33 @@ func UnmarshallMapGenSettingsJson(body []byte, w http.ResponseWriter) (mapGenSet 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/factorio/mapSettings.go b/src/factorio/mapSettings.go new file mode 100644 index 00000000..a3febd76 --- /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 int `json:"min_to_diffuse"` + Ageing int `json:"ageing"` + ExpectedMaxPerChunk int `json:"expected_max_per_chunk"` + MinToShowPerChunk int `json:"min_to_show_per_chunk"` + MinPollutionToDamageTrees int `json:"min_pollution_to_damage_trees"` + PollutionWithMaxForestDamage int `json:"pollution_with_max_forest_damage"` + PollutionPerTreeDamage int `json:"pollution_per_tree_damage"` + PollutionRestoredPerTreeDamage int `json:"pollution_restored_per_tree_damage"` + MaxPollutionToRestoreTrees int `json:"max_pollution_to_restore_trees"` + EnemyAttackPollutionConsumptionModifier int `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 4797d411..5f270928 100644 --- a/src/factorio/saves.go +++ b/src/factorio/saves.go @@ -85,7 +85,7 @@ func CreateSave(filePath string) (string, error) { return result, nil } -func GenerateMapPreview(mapSettingsPath string) (string, error) { +func GenerateMapPreview(mapSettingsGenFilePath string, mapSettingsFilePath string) (string, error) { if _, err := os.Stat("./map_previews"); err != nil { err = os.Mkdir("./map_previews", 664) @@ -101,7 +101,14 @@ func GenerateMapPreview(mapSettingsPath string) (string, error) { // source: https://wiki.factorio.com/Command_line_parameters previewFolder += string(filepath.Separator) - args := []string{"--map-gen-settings", mapSettingsPath, "--generate-map-preview", previewFolder} + args := []string{ + "--map-gen-settings", + mapSettingsGenFilePath, + "--generate-map-preview", + previewFolder, + "--map-settings", + mapSettingsFilePath, + } config := bootstrap.GetConfig() log.Println(filepath.Abs(config.FactorioBinary)) From 4471a6205fa1c06560f7adda14b3d678f98dc831 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Sun, 4 Apr 2021 23:40:47 +0200 Subject: [PATCH 05/13] use default settings to load map preview on mount --- src/api/handlers.go | 25 ++++++- src/api/routes.go | 12 ++++ ui/App/App.jsx | 14 ++-- ui/App/components/Layout.jsx | 8 +-- ui/App/components/Slider.jsx | 10 ++- ui/App/views/MapGenerator/MapGenerator.jsx | 70 ++++++++++++++++--- .../components/MapPreviewImage.jsx | 8 +++ .../MapGenerator/components/SeedInput.jsx | 22 +++--- .../rich-resources-map-gen-settings.js | 16 +++++ ui/App/views/MapGenerator/tabs/Advanced.jsx | 14 +++- .../MapGenerator/tabs/resources/Resources.jsx | 13 ++-- .../components/ResourceConfigurator.jsx | 12 ++-- ui/api/resources/saves.js | 14 +++- 13 files changed, 188 insertions(+), 50 deletions(-) create mode 100644 ui/App/views/MapGenerator/components/MapPreviewImage.jsx create mode 100644 ui/App/views/MapGenerator/settings/rich-resources-map-gen-settings.js diff --git a/src/api/handlers.go b/src/api/handlers.go index 703a3bcd..e8469abd 100644 --- a/src/api/handlers.go +++ b/src/api/handlers.go @@ -1,6 +1,7 @@ package api import ( + "encoding/base64" "encoding/json" "errors" "fmt" @@ -421,10 +422,19 @@ func CheckServer(w http.ResponseWriter, r *http.Request) { } } +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 != "" { @@ -433,6 +443,9 @@ func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { if mapSettingsFileName != "" { _ = os.Remove(mapSettingsFileName) } + if previewImagePath != "" { + _ = os.Remove(previewImagePath) + } WriteResponse(w, resp) }() @@ -473,7 +486,6 @@ func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { return } - var previewImagePath string previewImagePath, err = factorio.GenerateMapPreview(mapGenSettingsFileName, mapSettingsFileName) if err != nil { @@ -483,7 +495,16 @@ func GenerateMapPreview(w http.ResponseWriter, r *http.Request) { return } - resp = previewImagePath + 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) { diff --git a/src/api/routes.go b/src/api/routes.go index b973ecb1..430d91b5 100644 --- a/src/api/routes.go +++ b/src/api/routes.go @@ -175,6 +175,18 @@ var apiRoutes = Routes{ "/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/ui/App/App.jsx b/ui/App/App.jsx index 2fa842c7..b013696e 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"; @@ -49,7 +49,7 @@ const App = () => { } }, []); - const ProtectedRoute = useCallback(({component: Component, ...rest}) => ( + const ProtectedRoute = ({component: Component, ...rest}) => ( ( isAuthenticated && Component ? @@ -58,14 +58,20 @@ const App = () => { state: {from: props.location} }}/> )}/> - ), [isAuthenticated, serverStatus]); + ); + + useEffect(() => { + (async () => { + updateServerStatus() + })(); + }, []); return ( ()}/> - + diff --git a/ui/App/components/Layout.jsx b/ui/App/components/Layout.jsx index df73432c..9c1bbfd5 100644 --- a/ui/App/components/Layout.jsx +++ b/ui/App/components/Layout.jsx @@ -4,15 +4,11 @@ import Button from "./Button"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faBars} from "@fortawesome/free-solid-svg-icons"; -const Layout = ({children, handleLogout, serverStatus, updateServerStatus}) => { +const Layout = ({children, handleLogout, serverStatus}) => { const [isNavCollapsed, setIsNavCollapsed] = useState(true); - useEffect(() => { - (async () => { - updateServerStatus() - })(); - }, []); + const Status = ({info}) => { diff --git a/ui/App/components/Slider.jsx b/ui/App/components/Slider.jsx index cf810893..c7a77209 100644 --- a/ui/App/components/Slider.jsx +++ b/ui/App/components/Slider.jsx @@ -1,7 +1,13 @@ import React from "react"; -const Slider = () => { - return +const Slider = ({min = 1, max = 12, step = 1}) => { + return } export default Slider; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/MapGenerator.jsx b/ui/App/views/MapGenerator/MapGenerator.jsx index 62527ed5..437d5f70 100644 --- a/ui/App/views/MapGenerator/MapGenerator.jsx +++ b/ui/App/views/MapGenerator/MapGenerator.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, {useState, useEffect} from "react"; import TabControl from "../../components/Tabs/TabControl"; import Tab from "../../components/Tabs/Tab"; import Button from "../../components/Button"; @@ -9,34 +9,88 @@ import Advanced from "./tabs/Advanced"; import {useForm} from "react-hook-form"; import SeedInput from "./components/SeedInput"; import MapTypeSelect from "./components/MapTypeSelect"; +import saves from "../../../api/resources/saves"; +import MapPreviewImage from "./components/MapPreviewImage"; + const MapGenerator = () => { - const {register, handleSubmit} = useForm() + const {register, handleSubmit} = useForm(); + const [seed, setSeed] = useState(0); + const [settings, setSettings] = useState({}); + const [previewImage, setPreviewImage] = useState(null); + const [isLoadingPreview, setIsLoadingPreview] = useState(false); + + const loadPreview = () => { + setIsLoadingPreview(true) + + saves.preview(settings) + .then(imageData => setPreviewImage(imageData)) + .finally(() => setIsLoadingPreview(false)) + } + + const randomSeed = () => { + const randomValue = Math.floor(Math.random() * 1000000000) + updateSeed(randomValue) + } + + const updateSeed = value => { + setSeed(value) + setSettings(Object.assign(settings, {seed: value})) + } + + useEffect(() => { + console.log('test') + + Promise.all([ + saves.defaultMapGenSettings() + .then(mapGenSettings => setSettings(Object.assign(settings,mapGenSettings))), + saves.defaultMapSettings() + .then(mapSettings => setSettings(Object.assign(settings,mapSettings))), + + ]) + .finally(() => { + randomSeed() + loadPreview() + }) + + }, []); return null)}> Generate Map + } title={
- +
} > - +
+ + +
- +
+ + +
- +
+ + +
- +
+ + +
diff --git a/ui/App/views/MapGenerator/components/MapPreviewImage.jsx b/ui/App/views/MapGenerator/components/MapPreviewImage.jsx new file mode 100644 index 00000000..0278e2cd --- /dev/null +++ b/ui/App/views/MapGenerator/components/MapPreviewImage.jsx @@ -0,0 +1,8 @@ +import React from "react"; + + +const MapPreviewImage = ({imageData}) => { + return Map Preview Image +} + +export default MapPreviewImage; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/components/SeedInput.jsx b/ui/App/views/MapGenerator/components/SeedInput.jsx index 640b6965..690bab5c 100644 --- a/ui/App/views/MapGenerator/components/SeedInput.jsx +++ b/ui/App/views/MapGenerator/components/SeedInput.jsx @@ -1,30 +1,24 @@ -import React, {useState, useEffect} from "react"; -import Input from "../../../components/Input"; +import React from "react"; import Button from "../../../components/Button"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {faRandom} from "@fortawesome/free-solid-svg-icons"; -const SeedInput = ({inputRef}) => { - - const [seed, setSeed] = useState(0); - - const randomSeed = () => { - setSeed(Math.floor(Math.random() * 1000000000)) - } - useEffect(randomSeed, []); +const SeedInput = ({seed, updateSeed, generateRandomSeed}) => { return
+ onChange={event => { + const value = parseInt(event.target.value) + updateSeed(value) + }}/>
-
diff --git a/ui/App/views/MapGenerator/settings/rich-resources-map-gen-settings.js b/ui/App/views/MapGenerator/settings/rich-resources-map-gen-settings.js new file mode 100644 index 00000000..8d7cbfed --- /dev/null +++ b/ui/App/views/MapGenerator/settings/rich-resources-map-gen-settings.js @@ -0,0 +1,16 @@ +import defaultMapGenSettings from "./default-map-gen-settings"; + +export default { + ...defaultMapGenSettings, + "autoplace_controls": + { + "coal": {"frequency": 1, "size": 1, "richness": 2}, + "stone": {"frequency": 1, "size": 1, "richness": 2}, + "copper-ore": {"frequency": 1, "size": 1,"richness": 2}, + "iron-ore": {"frequency": 1, "size": 1, "richness": 2}, + "uranium-ore": {"frequency": 1, "size": 1, "richness": 2}, + "crude-oil": {"frequency": 1, "size": 1, "richness": 2}, + "trees": {"frequency": 1, "size": 1, "richness": 2}, + "enemy-base": {"frequency": 1, "size": 1, "richness": 2} + }, +} \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Advanced.jsx b/ui/App/views/MapGenerator/tabs/Advanced.jsx index a56ed195..2f754e70 100644 --- a/ui/App/views/MapGenerator/tabs/Advanced.jsx +++ b/ui/App/views/MapGenerator/tabs/Advanced.jsx @@ -1,7 +1,19 @@ import React from "react"; +import Label from "../../../components/Label"; +import Input from "../../../components/Input"; const Advanced = () => { - return <> + return
+
+ Map Size +
+
+
+
} export default Advanced; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx index a3819dba..393ef95c 100644 --- a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx @@ -2,7 +2,7 @@ import React from "react"; import ResourceConfigurator from "./components/ResourceConfigurator"; const Resources = () => { - return + return
@@ -12,11 +12,12 @@ const Resources = () => { - - - - - + + + + + +
Name
} diff --git a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx index 821890c7..09ca5012 100644 --- a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx @@ -1,16 +1,16 @@ import React from "react"; import Slider from "../../../../../components/Slider"; -const ResourceConfigurator = ({ name }) => { +const ResourceConfigurator = ({ label, namePrefix }) => { return - + {/**/} - {name} + {label} diff --git a/ui/api/resources/saves.js b/ui/api/resources/saves.js index d0cd79f8..1b1ccf29 100644 --- a/ui/api/resources/saves.js +++ b/ui/api/resources/saves.js @@ -9,7 +9,19 @@ export default { const response = await client.get(`/api/saves/rm/${save.name}`); return response.data; }, - create: async (name) => { + defaultMapSettings: async () => { + const response = await client.get('/api/saves/default-map-settings'); + return response.data; + }, + defaultMapGenSettings: async () => { + const response = await client.get('/api/saves/default-map-gen-settings'); + return response.data; + }, + preview: async mapSettings => { + const response = await client.post(`/api/saves/preview`, mapSettings); + return response.data; + }, + create: async name => { const response = await client.get(`/api/saves/create/${name}`); return response.data; }, From 79d73c19ea839ac1dcfd1572e773a91ea6b785a8 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Mon, 5 Apr 2021 22:44:38 +0200 Subject: [PATCH 06/13] load map preview when seed changes --- ui/App/views/MapGenerator/MapGenerator.jsx | 136 ++++++++++-------- .../components/MapPreviewImage.jsx | 24 +++- ui/App/views/MapGenerator/tabs/Advanced.jsx | 4 +- ui/App/views/MapGenerator/tabs/Enemy.jsx | 2 +- ui/App/views/MapGenerator/tabs/Terrain.jsx | 2 +- .../MapGenerator/tabs/resources/Resources.jsx | 4 +- 6 files changed, 106 insertions(+), 66 deletions(-) diff --git a/ui/App/views/MapGenerator/MapGenerator.jsx b/ui/App/views/MapGenerator/MapGenerator.jsx index 437d5f70..57a71ae1 100644 --- a/ui/App/views/MapGenerator/MapGenerator.jsx +++ b/ui/App/views/MapGenerator/MapGenerator.jsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from "react"; +import React, {useEffect, useState, useCallback} from "react"; import TabControl from "../../components/Tabs/TabControl"; import Tab from "../../components/Tabs/Tab"; import Button from "../../components/Button"; @@ -6,27 +6,42 @@ import Resources from "./tabs/resources/Resources"; import Terrain from "./tabs/Terrain"; import Enemy from "./tabs/Enemy"; import Advanced from "./tabs/Advanced"; -import {useForm} from "react-hook-form"; import SeedInput from "./components/SeedInput"; import MapTypeSelect from "./components/MapTypeSelect"; import saves from "../../../api/resources/saves"; import MapPreviewImage from "./components/MapPreviewImage"; +let timeoutPreviewHandle = null; const MapGenerator = () => { - const {register, handleSubmit} = useForm(); + const [isPreviewDisplayed, setIsPreviewDisplayed] = useState(false) const [seed, setSeed] = useState(0); const [settings, setSettings] = useState({}); const [previewImage, setPreviewImage] = useState(null); + const [previewImageSeed, setPreviewImageSeed] = useState(null); const [isLoadingPreview, setIsLoadingPreview] = useState(false); - const loadPreview = () => { - setIsLoadingPreview(true) - saves.preview(settings) - .then(imageData => setPreviewImage(imageData)) - .finally(() => setIsLoadingPreview(false)) + const loadPreview = (force = false) => { + if (isLoadingPreview || (!isPreviewDisplayed && !force)) { + return; + } + + const previewHandler = () => { + setIsLoadingPreview(true) + setPreviewImageSeed(settings.seed) + + saves.preview(settings) + .then(imageData => setPreviewImage(imageData)) + .finally(() => setIsLoadingPreview(false)) + } + + if (timeoutPreviewHandle) { + clearTimeout(timeoutPreviewHandle); + timeoutPreviewHandle = null + } + timeoutPreviewHandle = setTimeout(previewHandler, 600); } const randomSeed = () => { @@ -35,65 +50,70 @@ const MapGenerator = () => { } const updateSeed = value => { - setSeed(value) - setSettings(Object.assign(settings, {seed: value})) + setSeed(value); + setSettings(Object.assign(settings, {seed: value})); + loadPreview(); } useEffect(() => { - console.log('test') - Promise.all([ saves.defaultMapGenSettings() - .then(mapGenSettings => setSettings(Object.assign(settings,mapGenSettings))), + .then(mapGenSettings => setSettings(Object.assign(settings, mapGenSettings))), saves.defaultMapSettings() - .then(mapSettings => setSettings(Object.assign(settings,mapSettings))), - - ]) - .finally(() => { - randomSeed() - loadPreview() - }) + .then(mapSettings => setSettings(Object.assign(settings, mapSettings))), + ]).finally(() => { + randomSeed() + loadPreview() + }) }, []); - return
null)}> - Generate Map - } - title={ -
- - -
- } - > - -
- - -
-
- -
- - -
-
- -
- - -
-
- -
- - -
-
-
-
+ return + + {isPreviewDisplayed + ? + : + } +
+ } + title={ +
+ + +
+ } + > + +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ } export default MapGenerator; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/components/MapPreviewImage.jsx b/ui/App/views/MapGenerator/components/MapPreviewImage.jsx index 0278e2cd..5d7d0e3f 100644 --- a/ui/App/views/MapGenerator/components/MapPreviewImage.jsx +++ b/ui/App/views/MapGenerator/components/MapPreviewImage.jsx @@ -1,8 +1,28 @@ import React from "react"; +import {faSpinner} from "@fortawesome/free-solid-svg-icons"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -const MapPreviewImage = ({imageData}) => { - return Map Preview Image +const MapPreviewImage = ({imageData, show, isLoading, seed}) => { + return <> + {show &&
+ {isLoading && + <> +
+
+
+ +

Loading Map Preview

+
+
+ + } +
+

Seed: {seed}

+
+ +
} + } export default MapPreviewImage; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Advanced.jsx b/ui/App/views/MapGenerator/tabs/Advanced.jsx index 2f754e70..d8c8fecf 100644 --- a/ui/App/views/MapGenerator/tabs/Advanced.jsx +++ b/ui/App/views/MapGenerator/tabs/Advanced.jsx @@ -2,8 +2,8 @@ import React from "react"; import Label from "../../../components/Label"; import Input from "../../../components/Input"; -const Advanced = () => { - return
+const Advanced = ({settings, setSettings}) => { + return
Map Size
diff --git a/ui/App/views/MapGenerator/tabs/Enemy.jsx b/ui/App/views/MapGenerator/tabs/Enemy.jsx index c9a4e738..5f2bcedb 100644 --- a/ui/App/views/MapGenerator/tabs/Enemy.jsx +++ b/ui/App/views/MapGenerator/tabs/Enemy.jsx @@ -1,6 +1,6 @@ import React from "react"; -const Enemy = () => { +const Enemy = ({settings, setSettings}) => { return <> } diff --git a/ui/App/views/MapGenerator/tabs/Terrain.jsx b/ui/App/views/MapGenerator/tabs/Terrain.jsx index 7e122673..9c3c984c 100644 --- a/ui/App/views/MapGenerator/tabs/Terrain.jsx +++ b/ui/App/views/MapGenerator/tabs/Terrain.jsx @@ -1,6 +1,6 @@ import React from "react"; -const Terrain = () => { +const Terrain = ({settings, setSettings}) => { return <> } diff --git a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx index 393ef95c..5ea1a8c3 100644 --- a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx @@ -1,8 +1,8 @@ import React from "react"; import ResourceConfigurator from "./components/ResourceConfigurator"; -const Resources = () => { - return +const Resources = ({settings, setSettings}) => { + return
From 47300e4d90a35421fd5516e808d3d5f61e94df48 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Wed, 7 Apr 2021 13:49:04 +0200 Subject: [PATCH 07/13] load map preview when resource settings changes --- src/factorio/mapGenSettings.go | 6 +-- ui/App/components/Input.jsx | 5 ++- ui/App/views/MapGenerator/MapGenerator.jsx | 11 +++-- .../MapGenerator/tabs/resources/Resources.jsx | 14 +++---- .../components/ResourceConfigurator.jsx | 41 +++++++++++-------- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/factorio/mapGenSettings.go b/src/factorio/mapGenSettings.go index 63946f57..60eea524 100644 --- a/src/factorio/mapGenSettings.go +++ b/src/factorio/mapGenSettings.go @@ -1,9 +1,9 @@ package factorio type MapResource struct { - Frequency int `json:"frequency"` - Size int `json:"size"` - Richness int `json:"richness"` + Frequency float32 `json:"frequency"` + Size float32 `json:"size"` + Richness float32 `json:"richness"` } type AutoPlaceControls struct { diff --git a/ui/App/components/Input.jsx b/ui/App/components/Input.jsx index 4b5919dc..a851662a 100644 --- a/ui/App/components/Input.jsx +++ b/ui/App/components/Input.jsx @@ -4,8 +4,9 @@ const Input = ({ name, inputRef, placeholder = null, + className = null, type = "text", - defaultValue = null, + defaultValue = undefined, hasAutoComplete = true, onKeyDown = () => null, onChange = () => null, @@ -15,7 +16,7 @@ const Input = ({ }) => { return ( { const [previewImageSeed, setPreviewImageSeed] = useState(null); const [isLoadingPreview, setIsLoadingPreview] = useState(false); + const updateSettings = newSettings => { + setSettings(newSettings) + loadPreview(); + } + const loadPreview = (force = false) => { if (isLoadingPreview || (!isPreviewDisplayed && !force)) { @@ -91,19 +96,19 @@ const MapGenerator = () => { >
- +
- +
- +
diff --git a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx index 5ea1a8c3..3a93caec 100644 --- a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx @@ -5,19 +5,19 @@ const Resources = ({settings, setSettings}) => { return
Name
- + - - - - - - + + + + + +
NameName Frequency Size Richness
} diff --git a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx index 09ca5012..0fa3eb52 100644 --- a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx @@ -1,25 +1,34 @@ import React from "react"; -import Slider from "../../../../../components/Slider"; +import Input from "../../../../../components/Input"; -const ResourceConfigurator = ({ label, namePrefix }) => { - return - - {/**/} - - +const ResourceConfigurator = ({label, name, settings, setSettings }) => { + + const resource = settings?.autoplace_controls?.[name] + + const updateResource = (attribute, value) => { + let tmpSettings = JSON.parse(JSON.stringify(settings)); + tmpSettings.autoplace_controls[name][attribute] = parseFloat(value) + setSettings(tmpSettings); + } + + return + {label} - - + + { + updateResource('frequency', event.target.value) + }}/> - - + + { + updateResource('size', event.target.value) + }}/> - - + + { + updateResource('richness', event.target.value) + }}/> } From 79a5a0a6abb2a9137aea07488ecac4da45d6c84d Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Sun, 11 Apr 2021 18:22:20 +0200 Subject: [PATCH 08/13] wip - complete design of map generator --- tailwind.config.js | 3 + ui/App/components/Checkbox.jsx | 7 +- ui/App/components/Input.jsx | 8 +- ui/App/components/Label.jsx | 4 +- ui/App/components/Select.jsx | 4 +- ui/App/copy.js | 1 + ui/App/views/MapGenerator/MapGenerator.jsx | 2 +- .../components/MapPreviewImage.jsx | 14 +- ui/App/views/MapGenerator/tabs/Advanced.jsx | 153 +++++++++++++- ui/App/views/MapGenerator/tabs/Enemy.jsx | 198 +++++++++++++++++- ui/App/views/MapGenerator/tabs/Terrain.jsx | 103 ++++++++- .../MapGenerator/tabs/resources/Resources.jsx | 12 +- .../components/ResourceConfigurator.jsx | 10 +- 13 files changed, 482 insertions(+), 37 deletions(-) create mode 100644 ui/App/copy.js diff --git a/tailwind.config.js b/tailwind.config.js index 02a3752f..25707362 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,6 +12,9 @@ module.exports = { }, theme: { extend: { + minHeight: { + '1/2': '50%', + }, width: { 72: "18rem", 80: "20rem", diff --git a/ui/App/components/Checkbox.jsx b/ui/App/components/Checkbox.jsx index c1788366..8690f072 100644 --- a/ui/App/components/Checkbox.jsx +++ b/ui/App/components/Checkbox.jsx @@ -1,15 +1,16 @@ import React from "react"; -const Checkbox = ({name, text, inputRef, checked}) => { +const Checkbox = ({name, text, inputRef, checked, className, textSize = 'sm', onChange = null}) => { return ( -
-
-
} diff --git a/ui/App/views/MapGenerator/tabs/Enemy.jsx b/ui/App/views/MapGenerator/tabs/Enemy.jsx index 5f2bcedb..f32eda1f 100644 --- a/ui/App/views/MapGenerator/tabs/Enemy.jsx +++ b/ui/App/views/MapGenerator/tabs/Enemy.jsx @@ -1,7 +1,203 @@ import React from "react"; +import Input from "../../../components/Input"; +import Checkbox from "../../../components/Checkbox"; +import Label from "../../../components/Label"; +import copy from "../../../copy"; const Enemy = ({settings, setSettings}) => { - return <> + return
+
+ + + + + + + + + + + + + + +
+ FrequencySize
+ { + let tmp = copy(settings); + tmp.autoplace_controls['enemy-base'].richness = event.target.checked ? 1 : 0; + setSettings(tmp); + }} + /> + + { + let tmp = copy(settings); + tmp.autoplace_controls['enemy-base'].frequency = parseInt(event.target.value); + setSettings(tmp); + }} + /> + + { + let tmp = copy(settings); + tmp.autoplace_controls['enemy-base'].size = parseInt(event.target.value); + setSettings(tmp); + }} + /> +
+
+
+ +
+
+
+
+ { + let tmp = copy(settings); + tmp.starting_area = parseInt(event.target.value); + setSettings(tmp); + }} + /> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ { + let tmp = copy(settings) + tmp.enemy_expansion.enabled = event.target.checked; + setSettings(tmp) + }} + /> +
+ + +
+ + +
+ + +
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ { + let tmp = copy(settings) + tmp.enemy_evolution.enabled = event.target.checked; + setSettings(tmp) + }} + /> +
+ + { + let tmp = copy(settings) + tmp.enemy_evolution.time_factor = event.target.value; + setSettings(tmp) + }} + /> +
+ + { + let tmp = copy(settings) + tmp.enemy_evolution.destroy_factor = event.target.value; + setSettings(tmp) + }} + /> +
+ + { + let tmp = copy(settings) + tmp.enemy_evolution.pollution_factor = event.target.value; + setSettings(tmp) + }} + /> +
+
+
} export default Enemy; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/Terrain.jsx b/ui/App/views/MapGenerator/tabs/Terrain.jsx index 9c3c984c..f165cdb1 100644 --- a/ui/App/views/MapGenerator/tabs/Terrain.jsx +++ b/ui/App/views/MapGenerator/tabs/Terrain.jsx @@ -1,7 +1,108 @@ import React from "react"; +import Select from "../../../components/Select"; +import Checkbox from "../../../components/Checkbox"; +import Input from "../../../components/Input"; +import Label from "../../../components/Label"; +import copy from "../../../copy"; const Terrain = ({settings, setSettings}) => { - return <> + return
+
+
+
+ + + + + + + + + + +
+
+ + + + + + + + + + + + + + +
+ FrequencyContinuity
+ + + { + let tmp = copy(settings); + tmp.cliff_settings.cliff_elevation_interval = 40 / parseInt(event.target.value); + setSettings(tmp); + }} + /> + + { + let tmp = copy(settings); + tmp.cliff_settings.richness = parseInt(event.target.value); + setSettings(tmp); + }} + /> +
+
+
+ + + + + + + + + + + + + + + + + + + +
+ ScaleBias
Moisture
Terrain Typ
+
+
} export default Terrain; \ No newline at end of file diff --git a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx index 3a93caec..8809c0b7 100644 --- a/ui/App/views/MapGenerator/tabs/resources/Resources.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/Resources.jsx @@ -2,13 +2,13 @@ import React from "react"; import ResourceConfigurator from "./components/ResourceConfigurator"; const Resources = ({settings, setSettings}) => { - return + return
- - - - - + + + + diff --git a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx index 0fa3eb52..078dd984 100644 --- a/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx +++ b/ui/App/views/MapGenerator/tabs/resources/components/ResourceConfigurator.jsx @@ -11,21 +11,21 @@ const ResourceConfigurator = ({label, name, settings, setSettings }) => { setSettings(tmpSettings); } - return - + - - - @@ -99,7 +107,15 @@ const Enemy = ({settings, setSettings}) => { @@ -107,7 +123,15 @@ const Enemy = ({settings, setSettings}) => { @@ -115,7 +139,15 @@ const Enemy = ({settings, setSettings}) => { @@ -123,7 +155,15 @@ const Enemy = ({settings, setSettings}) => { diff --git a/ui/App/views/MapGenerator/tabs/Terrain.jsx b/ui/App/views/MapGenerator/tabs/Terrain.jsx index f165cdb1..9438323a 100644 --- a/ui/App/views/MapGenerator/tabs/Terrain.jsx +++ b/ui/App/views/MapGenerator/tabs/Terrain.jsx @@ -4,6 +4,7 @@ import Checkbox from "../../../components/Checkbox"; import Input from "../../../components/Input"; import Label from "../../../components/Label"; import copy from "../../../copy"; +import {parse} from "@fortawesome/fontawesome-svg-core"; const Terrain = ({settings, setSettings}) => { return
@@ -11,10 +12,31 @@ const Terrain = ({settings, setSettings}) => {
- { + let tmp = copy(settings); + + switch (event.target.value) { + case 'island': + tmp.property_expression_names.elevation = '0_17-island' + break; + case 'terrain-0.16': + tmp.property_expression_names.elevation = '0_16-elevation'; + break; + case 'normal': + default: + tmp.property_expression_names.elevation = ''; + break; + } + + setSettings(tmp); + }} + />
NameFrequencySizeRichness
+ FrequencySizeRichness
+ return
{label} + { updateResource('frequency', event.target.value) }}/> + { updateResource('size', event.target.value) }}/> + { updateResource('richness', event.target.value) }}/> From 4c0cf2d0d5b7a4138ad48d5e507b3b7e32d1647f Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Mon, 12 Apr 2021 21:23:08 +0200 Subject: [PATCH 09/13] settings are all modifiable at map generator --- src/factorio/mapGenSettings.go | 1 + ui/App/components/Select.jsx | 11 +- ui/App/views/MapGenerator/MapGenerator.jsx | 29 +++- ui/App/views/MapGenerator/tabs/Advanced.jsx | 49 +++++-- ui/App/views/MapGenerator/tabs/Enemy.jsx | 52 ++++++- ui/App/views/MapGenerator/tabs/Terrain.jsx | 155 ++++++++++++++++++-- 6 files changed, 251 insertions(+), 46 deletions(-) diff --git a/src/factorio/mapGenSettings.go b/src/factorio/mapGenSettings.go index 60eea524..8d099f2f 100644 --- a/src/factorio/mapGenSettings.go +++ b/src/factorio/mapGenSettings.go @@ -25,6 +25,7 @@ type CliffSettings struct { } 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"` diff --git a/ui/App/components/Select.jsx b/ui/App/components/Select.jsx index a445faf1..b48fe0f6 100644 --- a/ui/App/components/Select.jsx +++ b/ui/App/components/Select.jsx @@ -1,9 +1,16 @@ import React, {useState} from "react"; -const Select = ({name, inputRef, options, isInline = false, className = "", defaultValue = ""}) => { +const Select = ({name, inputRef, options, onChange= null, isInline = false, className = "", defaultValue = ""}) => { const [value, setValue] = useState(defaultValue); + const updateValue = event => { + if (onChange !== null) { + onChange(event); + } + setValue(event.target.value) + } + return (
diff --git a/ui/App/views/MapGenerator/MapGenerator.jsx b/ui/App/views/MapGenerator/MapGenerator.jsx index a796ca3e..f603d556 100644 --- a/ui/App/views/MapGenerator/MapGenerator.jsx +++ b/ui/App/views/MapGenerator/MapGenerator.jsx @@ -10,6 +10,7 @@ import SeedInput from "./components/SeedInput"; import MapTypeSelect from "./components/MapTypeSelect"; import saves from "../../../api/resources/saves"; import MapPreviewImage from "./components/MapPreviewImage"; +import copy from "../../copy"; let timeoutPreviewHandle = null; @@ -22,13 +23,15 @@ const MapGenerator = () => { const [previewImageSeed, setPreviewImageSeed] = useState(null); const [isLoadingPreview, setIsLoadingPreview] = useState(false); - const updateSettings = newSettings => { + const updateSettings = (newSettings, shouldLoadPreview = true) => { setSettings(newSettings) - loadPreview(); + if (shouldLoadPreview) { + loadPreview(newSettings) + } } - const loadPreview = (force = false) => { + const loadPreview = (settings, force = false) => { if (isLoadingPreview || (!isPreviewDisplayed && !force)) { return; } @@ -37,7 +40,17 @@ const MapGenerator = () => { setIsLoadingPreview(true) setPreviewImageSeed(settings.seed) - saves.preview(settings) + let tmpSettings = copy(settings); + + if (!tmpSettings.cliff_settings.enabled) { + tmpSettings.cliff_settings.richness = 0; + } + + if (!tmpSettings.water_enabled) { + tmpSettings.water = 0; + } + + saves.preview(tmpSettings) .then(imageData => setPreviewImage(imageData)) .finally(() => setIsLoadingPreview(false)) } @@ -56,8 +69,9 @@ const MapGenerator = () => { const updateSeed = value => { setSeed(value); - setSettings(Object.assign(settings, {seed: value})); - loadPreview(); + const newSettings = Object.assign(settings, {seed: value}); + setSettings(newSettings); + loadPreview(newSettings); } useEffect(() => { @@ -69,7 +83,6 @@ const MapGenerator = () => { ]).finally(() => { randomSeed() - loadPreview() }) }, []); @@ -81,7 +94,7 @@ const MapGenerator = () => { ? : } diff --git a/ui/App/views/MapGenerator/tabs/Advanced.jsx b/ui/App/views/MapGenerator/tabs/Advanced.jsx index 43b938cd..0407bf29 100644 --- a/ui/App/views/MapGenerator/tabs/Advanced.jsx +++ b/ui/App/views/MapGenerator/tabs/Advanced.jsx @@ -42,10 +42,17 @@ const Advanced = ({settings, setSettings}) => {
@@ -55,10 +62,17 @@ const Advanced = ({settings, setSettings}) => {
diff --git a/ui/App/views/MapGenerator/tabs/Enemy.jsx b/ui/App/views/MapGenerator/tabs/Enemy.jsx index f32eda1f..2db1aecd 100644 --- a/ui/App/views/MapGenerator/tabs/Enemy.jsx +++ b/ui/App/views/MapGenerator/tabs/Enemy.jsx @@ -88,10 +88,18 @@ const Enemy = ({settings, setSettings}) => {
- - + { + let tmp = copy(settings) + tmp.enemy_expansion.max_expansion_distance = parseInt(event.target.value); + setSettings(tmp) + }} + />
- + { + let tmp = copy(settings) + tmp.enemy_expansion.settler_group_min_size = parseInt(event.target.value); + setSettings(tmp) + }} + />
- + { + let tmp = copy(settings) + tmp.enemy_expansion.settler_group_max_size = parseInt(event.target.value); + setSettings(tmp) + }} + />
- + { + let tmp = copy(settings) + tmp.enemy_expansion.min_expansion_cooldown = parseInt(event.target.value); + setSettings(tmp) + }} + />
- + { + let tmp = copy(settings) + tmp.enemy_expansion.max_expansion_cooldown = parseInt(event.target.value); + setSettings(tmp) + }} + />
@@ -27,14 +49,72 @@ const Terrain = ({settings, setSettings}) => { - - - + + + - - - + + +
+ { + let tmp = copy(settings); + tmp.water_enabled = event.target.checked; + setSettings(tmp); + }} + /> + + { + let tmp = copy(settings); + tmp.water = 1 / parseInt(event.target.value); + setSettings(tmp); + }} + /> + + { + let tmp = copy(settings); + tmp.water = parseInt(event.target.value); + setSettings(tmp); + }} + /> +
+ { + let tmp = copy(settings); + tmp.autoplace_controls.trees.richness = event.target.checked ? 1 : 0; + setSettings(tmp) + }} + /> + + { + let tmp = copy(settings); + tmp.autoplace_controls.trees.size = parseFloat(event.target.value); + setSettings(tmp) + }} + /> + + { + let tmp = copy(settings); + tmp.autoplace_controls.trees.frequency = parseFloat(event.target.value); + setSettings(tmp) + }} + /> +
@@ -52,12 +132,18 @@ const Terrain = ({settings, setSettings}) => { { + let tmp = copy(settings); + tmp.cliff_settings.enabled = event.target.checked; + setSettings(tmp); + }} /> { let tmp = copy(settings); tmp.cliff_settings.cliff_elevation_interval = 40 / parseInt(event.target.value); @@ -68,6 +154,7 @@ const Terrain = ({settings, setSettings}) => { { let tmp = copy(settings); tmp.cliff_settings.richness = parseInt(event.target.value); @@ -91,13 +178,49 @@ const Terrain = ({settings, setSettings}) => { Moisture - - + + { + let tmp = copy(settings); + tmp.property_expression_names['control-setting:moisture:frequency:multiplier'] = event.target.value; + setSettings(tmp); + }} + /> + + + { + let tmp = copy(settings); + tmp.property_expression_names['control-setting:moisture:bias'] = event.target.value; + setSettings(tmp); + }} + /> + Terrain Typ - - + + { + let tmp = copy(settings); + tmp.property_expression_names['control-setting:aux:frequency:multiplier'] = `${1 / event.target.value}`; + setSettings(tmp); + }} + /> + + + { + let tmp = copy(settings); + tmp.property_expression_names['control-setting:aux:bias'] = event.target.value; + setSettings(tmp); + }} + /> + From 8aacbbcd08354d123f70758e25da67349d0242bb Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Mon, 19 Apr 2021 22:05:28 +0200 Subject: [PATCH 10/13] add functionality to map template selection --- src/factorio/mapGenSettings.go | 2 +- src/factorio/mapSettings.go | 20 ++-- ui/App/components/Checkbox.jsx | 25 +++- ui/App/components/Select.jsx | 17 ++- ui/App/views/MapGenerator/MapGenerator.jsx | 4 +- .../MapGenerator/components/MapTypeSelect.jsx | 110 +++++++++++++++++- ui/App/views/MapGenerator/tabs/Advanced.jsx | 4 +- ui/App/views/MapGenerator/tabs/Enemy.jsx | 16 ++- ui/App/views/MapGenerator/tabs/Terrain.jsx | 14 +-- .../components/ResourceConfigurator.jsx | 3 +- 10 files changed, 175 insertions(+), 40 deletions(-) diff --git a/src/factorio/mapGenSettings.go b/src/factorio/mapGenSettings.go index 8d099f2f..32dd8e6c 100644 --- a/src/factorio/mapGenSettings.go +++ b/src/factorio/mapGenSettings.go @@ -42,7 +42,7 @@ type MapGenSettings struct { Water int `json:"water"` Width int `json:"width"` Height int `json:"height"` - StartingArea int `json:"starting_area"` + StartingArea float32 `json:"starting_area"` PeacefulMode bool `json:"peaceful_mode"` AutoPlaceControls AutoPlaceControls `json:"autoplace_controls"` diff --git a/src/factorio/mapSettings.go b/src/factorio/mapSettings.go index a3febd76..45e53b59 100644 --- a/src/factorio/mapSettings.go +++ b/src/factorio/mapSettings.go @@ -10,16 +10,16 @@ type DifficultySettings struct { type Pollution struct { Enabled bool `json:"enabled"` DiffusionRatio float32 `json:"diffusion_ratio"` - MinToDiffuse int `json:"min_to_diffuse"` - Ageing int `json:"ageing"` - ExpectedMaxPerChunk int `json:"expected_max_per_chunk"` - MinToShowPerChunk int `json:"min_to_show_per_chunk"` - MinPollutionToDamageTrees int `json:"min_pollution_to_damage_trees"` - PollutionWithMaxForestDamage int `json:"pollution_with_max_forest_damage"` - PollutionPerTreeDamage int `json:"pollution_per_tree_damage"` - PollutionRestoredPerTreeDamage int `json:"pollution_restored_per_tree_damage"` - MaxPollutionToRestoreTrees int `json:"max_pollution_to_restore_trees"` - EnemyAttackPollutionConsumptionModifier int `json:"enemy_attack_pollution_consumption_modifier"` + 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 { diff --git a/ui/App/components/Checkbox.jsx b/ui/App/components/Checkbox.jsx index 8690f072..aa24cbd1 100644 --- a/ui/App/components/Checkbox.jsx +++ b/ui/App/components/Checkbox.jsx @@ -1,6 +1,23 @@ -import React from "react"; +import React, {useState, useEffect} from "react"; const Checkbox = ({name, text, inputRef, 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]); + return ( ) diff --git a/ui/App/components/Select.jsx b/ui/App/components/Select.jsx index b48fe0f6..f6006762 100644 --- a/ui/App/components/Select.jsx +++ b/ui/App/components/Select.jsx @@ -1,16 +1,23 @@ -import React, {useState} from "react"; +import React, {useState, useEffect} from "react"; -const Select = ({name, inputRef, options, onChange= null, isInline = false, className = "", defaultValue = ""}) => { +const Select = ({name, inputRef = null, options, onChange= null, isInline = false, className = "", defaultValue = null, value = null}) => { - const [value, setValue] = useState(defaultValue); + const [selectedValue, setSelectedValue] = useState(defaultValue || ""); const updateValue = event => { if (onChange !== null) { + event.persist(); onChange(event); } - setValue(event.target.value) + setSelectedValue(event.target.value); } + useEffect(() => { + if (value !== null) { + setSelectedValue(value); + } + }, [value]) + return (
{ value={settings?.pollution?.absorption_modifier || 0} onChange={event => { let tmp = copy(settings) - tmp.pollution.absorption_modifier = event.target.value; + tmp.pollution.absorption_modifier = parseFloat(event.target.value); setSettings(tmp) }} /> @@ -128,7 +128,7 @@ const Advanced = ({settings, setSettings}) => { value={settings?.pollution?.attack_cost_modifier || 0} onChange={event => { let tmp = copy(settings) - tmp.pollution.attack_cost_modifier = event.target.value; + tmp.pollution.attack_cost_modifier = parseFloat(event.target.value); setSettings(tmp) }} /> diff --git a/ui/App/views/MapGenerator/tabs/Enemy.jsx b/ui/App/views/MapGenerator/tabs/Enemy.jsx index 2db1aecd..af97e8a8 100644 --- a/ui/App/views/MapGenerator/tabs/Enemy.jsx +++ b/ui/App/views/MapGenerator/tabs/Enemy.jsx @@ -19,7 +19,7 @@ const Enemy = ({settings, setSettings}) => { { let tmp = copy(settings); tmp.autoplace_controls['enemy-base'].richness = event.target.checked ? 1 : 0; @@ -29,7 +29,7 @@ const Enemy = ({settings, setSettings}) => { { let tmp = copy(settings); @@ -54,7 +54,15 @@ const Enemy = ({settings, setSettings}) => {
- + { + let tmp = copy(settings); + tmp.peaceful_mode = event.target.checked; + setSettings(tmp); + }} + />
@@ -75,7 +83,7 @@ const Enemy = ({settings, setSettings}) => { { let tmp = copy(settings) tmp.enemy_expansion.enabled = event.target.checked; diff --git a/ui/App/views/MapGenerator/tabs/Terrain.jsx b/ui/App/views/MapGenerator/tabs/Terrain.jsx index 9438323a..a05707c9 100644 --- a/ui/App/views/MapGenerator/tabs/Terrain.jsx +++ b/ui/App/views/MapGenerator/tabs/Terrain.jsx @@ -4,7 +4,6 @@ import Checkbox from "../../../components/Checkbox"; import Input from "../../../components/Input"; import Label from "../../../components/Label"; import copy from "../../../copy"; -import {parse} from "@fortawesome/fontawesome-svg-core"; const Terrain = ({settings, setSettings}) => { return
@@ -13,19 +12,20 @@ const Terrain = ({settings, setSettings}) => {
{ const [previewImage, setPreviewImage] = useState(null); const [previewImageSeed, setPreviewImageSeed] = useState(null); const [isLoadingPreview, setIsLoadingPreview] = useState(false); + const [saveFileName, setSaveFileName] = useState(""); + const [isGeneratingMap, setIsGeneratingMap] = useState(false); const updateSettings = (newSettings, shouldLoadPreview = true) => { setSettings(newSettings); @@ -30,6 +33,12 @@ const MapGenerator = () => { } } + const createSave = () => { + setIsGeneratingMap(true); + saves.create(saveFileName, settings) + .then(() => flash(`Save "${saveFileName}" created`, "green")) + .finally(() => setIsGeneratingMap(false)); + } const loadPreview = (settings, force = false) => { if (isLoadingPreview || (!isPreviewDisplayed && !force)) { @@ -80,7 +89,6 @@ const MapGenerator = () => { .then(mapGenSettings => setSettings(Object.assign(settings, mapGenSettings))), saves.defaultMapSettings() .then(mapSettings => setSettings(Object.assign(settings, mapSettings))), - ]).finally(() => { randomSeed() }) @@ -89,7 +97,24 @@ const MapGenerator = () => { return - +
+ setSaveFileName(event.target.value)} + /> + +
{isPreviewDisplayed ? : - - ) -} - -export default CreateSaveForm; \ No newline at end of file diff --git a/ui/App/views/Saves/components/UploadSaveForm.jsx b/ui/App/views/Saves/components/UploadSaveForm.jsx deleted file mode 100644 index 6fe57c02..00000000 --- a/ui/App/views/Saves/components/UploadSaveForm.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import Button from "../../../components/Button"; -import React, {useState} from "react"; -import {useForm} from "react-hook-form"; -import saves from "../../../../api/resources/saves"; -import Error from "../../../components/Error"; -import Label from "../../../components/Label"; - - -const UploadSaveForm = ({onSuccess}) => { - const {register, handleSubmit, errors} = useForm(); - const [fileName, setFileName] = useState('Select File ...'); - - const onSubmit = (data, e) => { - saves.upload(data.savefile[0]).then(_ => { - e.target.reset(); - onSuccess(); - }) - }; - - return ( -
-
-
- -
- ) -} - -export default UploadSaveForm; \ No newline at end of file diff --git a/ui/App/views/Saves/components/UploadSaveModal.js b/ui/App/views/Saves/components/UploadSaveModal.js new file mode 100644 index 00000000..c923ff1c --- /dev/null +++ b/ui/App/views/Saves/components/UploadSaveModal.js @@ -0,0 +1,60 @@ +import Button from "../../../components/Button"; +import React, {useState} from "react"; +import {useForm} from "react-hook-form"; +import saves from "../../../../api/resources/saves"; +import Error from "../../../components/Error"; +import Label from "../../../components/Label"; +import Modal from "../../../components/Modal"; + + +const UploadSaveModal = ({onSuccess, close, isOpen}) => { + const {register, handleSubmit, errors} = useForm(); + const [fileName, setFileName] = useState('Select File ...'); + const [isUploading, setIsUploading] = useState(false); + + const onSubmit = (data, e) => { + setIsUploading(true); + saves + .upload(data.savefile[0]).then(() => { + e.target.reset(); + onSuccess(); + }) + .finally(() => setIsUploading(false)); + }; + + return ( +
+ +
+ } + actions={ + <> + + + + } + /> + + + ) +} + +export default UploadSaveModal; \ No newline at end of file diff --git a/ui/api/resources/saves.js b/ui/api/resources/saves.js index 1b1ccf29..ca44d6b8 100644 --- a/ui/api/resources/saves.js +++ b/ui/api/resources/saves.js @@ -21,8 +21,8 @@ export default { const response = await client.post(`/api/saves/preview`, mapSettings); return response.data; }, - create: async name => { - const response = await client.get(`/api/saves/create/${name}`); + create: async (name, settings) => { + const response = await client.post(`/api/saves/create/${name}`, settings); return response.data; }, upload: async file => { From a49c1f6a17d5ed21010c28a4237df39cd76fb7a4 Mon Sep 17 00:00:00 2001 From: Jan Naahs Date: Wed, 21 Apr 2021 21:12:50 +0200 Subject: [PATCH 12/13] improved UploadSaveModal.js --- ui/App/components/Modal.jsx | 25 +++++++++++++------ ui/App/views/Saves/Saves.jsx | 22 ++++++---------- .../views/Saves/components/UploadSaveModal.js | 22 +++++++++------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/ui/App/components/Modal.jsx b/ui/App/components/Modal.jsx index e0c2b15f..68a46f9a 100644 --- a/ui/App/components/Modal.jsx +++ b/ui/App/components/Modal.jsx @@ -4,17 +4,28 @@ import * as ReactDom from "react-dom"; const modalRoot = document.getElementById('modal-root'); -const Modal = ({title, content, isOpen, actions = null}) => { +const Modal = ({title, content, isOpen, actions = null, onSubmit = null}) => { return ReactDom.createPortal((isOpen &&
- + {onSubmit + ?
+ + + : + } +
), modalRoot) } diff --git a/ui/App/views/Saves/Saves.jsx b/ui/App/views/Saves/Saves.jsx index d5c454b3..06ef1e4a 100644 --- a/ui/App/views/Saves/Saves.jsx +++ b/ui/App/views/Saves/Saves.jsx @@ -6,29 +6,21 @@ import {faDownload, faTrashAlt} from "@fortawesome/free-solid-svg-icons"; import Button from "../../components/Button"; import UploadSaveModal from "./components/UploadSaveModal"; -const Saves = ({serverStatus}) => { +const Saves = () => { const [saves, setSaves] = useState([]); const [isSaveUploadModalOpen, setIsSaveUploadModalOpen] = useState(false); const updateList = () => { savesResource.list() - .then(res => { - if (res) { - setSaves(res); - } - }) + .then(setSaves) } - useEffect(() => { - updateList() - }, []); + useEffect(updateList, []); - const deleteSave = async (save) => { - const res = await savesResource.delete(save); - if (res) { - updateList() - } + const deleteSave = save => { + savesResource.delete(save) + .then(updateList); } return ( @@ -38,7 +30,7 @@ const Saves = ({serverStatus}) => { actions={ <> - setIsSaveUploadModalOpen(false)}/> + setIsSaveUploadModalOpen(false)}/> } content={ diff --git a/ui/App/views/Saves/components/UploadSaveModal.js b/ui/App/views/Saves/components/UploadSaveModal.js index c923ff1c..2a3862b0 100644 --- a/ui/App/views/Saves/components/UploadSaveModal.js +++ b/ui/App/views/Saves/components/UploadSaveModal.js @@ -6,27 +6,33 @@ import Error from "../../../components/Error"; import Label from "../../../components/Label"; import Modal from "../../../components/Modal"; +const defaultFileName = "Select File ..." const UploadSaveModal = ({onSuccess, close, isOpen}) => { const {register, handleSubmit, errors} = useForm(); - const [fileName, setFileName] = useState('Select File ...'); + const [fileName, setFileName] = useState(defaultFileName); const [isUploading, setIsUploading] = useState(false); - const onSubmit = (data, e) => { + const onSubmit = data => { setIsUploading(true); saves - .upload(data.savefile[0]).then(() => { - e.target.reset(); + .upload(data.savefile[0]) + .then(message => { + flash(message, "green"); onSuccess(); + close(); }) - .finally(() => setIsUploading(false)); + .finally(() => { + setFileName(defaultFileName); + setIsUploading(false); + }); }; return ( -
{isPreviewDisplayed ?