From b515c32f98b1c5ae4696caa7920ebb672c952c81 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Sat, 4 Jan 2020 15:33:43 +0100 Subject: [PATCH] Multi stop route planning (#2) * Add support for multi-job creation Signed-off-by: Knut Ahlers * Fix: Ensure at least one unit of cargo is transported Signed-off-by: Knut Ahlers * Multi-stop route planning Signed-off-by: Knut Ahlers * Linter + fix of weird char deletion Signed-off-by: Knut Ahlers --- cmd/sii-editor/api_saves.go | 96 +++++++----------------------- cmd/sii-editor/frontend/app.js | 36 +++++++++-- cmd/sii-editor/frontend/index.html | 54 +++++++++++++++-- cmd/sii-editor/saves.go | 80 +++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 85 deletions(-) diff --git a/cmd/sii-editor/api_saves.go b/cmd/sii-editor/api_saves.go index 5e15e6c..41b95d7 100644 --- a/cmd/sii-editor/api_saves.go +++ b/cmd/sii-editor/api_saves.go @@ -30,11 +30,11 @@ func init() { func handleAddJob(w http.ResponseWriter, r *http.Request) { var ( - job commSaveJob + jobs []commSaveJob vars = mux.Vars(r) ) - if err := json.NewDecoder(r.Body).Decode(&job); err != nil { + if err := json.NewDecoder(r.Body).Decode(&jobs); err != nil { apiGenericError(w, http.StatusBadRequest, errors.Wrap(err, "Unable to decode input data")) return } @@ -48,82 +48,32 @@ func handleAddJob(w http.ResponseWriter, r *http.Request) { info.SaveName = storeSaveName info.FileTime = time.Now().Unix() - // Set urgency if it isn't - if job.Urgency == nil { - u := int64(0) - job.Urgency = &u - } - - if job.Weight == 0 { - job.Weight = 10000 // 10 tons as a default - } - - if job.Weight < 100 { - // User clearly did't want 54kg but 54 tons! (If not: screw em) - job.Weight *= 1000 - } - - if job.Distance == 0 { - job.Distance = 100 // If the user did not provide distance use 100km as a default - } - - // Get company - company := game.BlockByName(job.OriginReference).(*sii.Company) - // Get cargo - cargo := baseGameUnit.BlockByName(job.CargoReference).(*sii.CargoData) - - // Get trailer / truck from other jobs - var cTruck, cTV, cTD string - - for _, jp := range company.JobOffer { - j := jp.Resolve().(*sii.JobOfferData) - if j.CompanyTruck.IsNull() || j.TrailerVariant.IsNull() || j.TrailerDefinition.IsNull() { - continue + for i, job := range jobs { + // Set urgency if it isn't + if job.Urgency == nil { + u := int64(0) + job.Urgency = &u } - cTruck, cTV, cTD = j.CompanyTruck.Target, j.TrailerVariant.Target, j.TrailerDefinition.Target - break - } - if cTruck == "" || cTV == "" || cTD == "" { - // The company did not have any valid job offers to steal from, lets search globally - for _, jb := range game.BlocksByClass("job_offer_data") { - j := jb.(*sii.JobOfferData) - if j.CompanyTruck.IsNull() || j.TrailerVariant.IsNull() || j.TrailerDefinition.IsNull() { - continue - } - cTruck, cTV, cTD = j.CompanyTruck.Target, j.TrailerVariant.Target, j.TrailerDefinition.Target - break + if job.Weight == 0 { + job.Weight = 10000 // 10 tons as a default + } + + if job.Weight < 100 { + // User clearly did't want 54kg but 54 tons! (If not: screw em) + job.Weight *= 1000 + } + + if job.Distance == 0 { + job.Distance = 100 // If the user did not provide distance use 100km as a default + } + + if err = addJobToGame(game, job, i); err != nil { + apiGenericError(w, http.StatusInternalServerError, errors.Wrap(err, "Unable to add job")) + return } } - jobID := "_nameless." + strconv.FormatInt(time.Now().Unix(), 16) - exTime := game.BlocksByClass("economy")[0].(*sii.Economy).GameTime + 300 // 300min = 5h - j := &sii.JobOfferData{ - // User requested job data - Target: strings.TrimPrefix(job.TargetReference, "company.volatile."), - ExpirationTime: &exTime, - Urgency: job.Urgency, - Cargo: sii.Ptr{Target: job.CargoReference}, - UnitsCount: int64(job.Weight / cargo.Mass), - ShortestDistanceKM: job.Distance, - - // Some static data - FillRatio: 1, // Dunno but other jobs have it at 1, so keep for now - - // Dunno where this data comes from, it works without it and gets own ones - TrailerPlace: []sii.Placement{}, - - // Too lazy to implement, just steal it too - CompanyTruck: sii.Ptr{Target: cTruck}, - TrailerVariant: sii.Ptr{Target: cTV}, - TrailerDefinition: sii.Ptr{Target: cTD}, - } - j.Init("", jobID) - - // Add the new job to the game - game.Entries = append(game.Entries, j) - company.JobOffer = append([]sii.Ptr{{Target: j.Name()}}, company.JobOffer...) - // Write the save-game if err = storeSave(vars["profileID"], storeSaveFolder, game, info); err != nil { apiGenericError(w, http.StatusInternalServerError, errors.Wrap(err, "Unable to store save")) diff --git a/cmd/sii-editor/frontend/app.js b/cmd/sii-editor/frontend/app.js index ab3894c..1e5505d 100644 --- a/cmd/sii-editor/frontend/app.js +++ b/cmd/sii-editor/frontend/app.js @@ -26,7 +26,7 @@ window.app = new Vue({ for (const ref in this.companies) { result.push({ value: ref, - text: `${this.companies[ref].city}, ${this.companies[ref].name}`, + text: this.companyNameFromReference(ref), }) } @@ -153,6 +153,7 @@ window.app = new Vue({ companies: {}, jobs: [], newJob: { weight: 10 }, + plannedRoute: [], profiles: {}, save: null, saveLoading: false, @@ -177,6 +178,10 @@ window.app = new Vue({ }) }, + companyNameFromReference(ref) { + return `${this.companies[ref].city}, ${this.companies[ref].name}` + }, + createJob() { if (!this.companies[this.newJob.origin_reference]) { this.showToast('Uhm…', 'Source Company does not exist', 'danger') @@ -199,15 +204,19 @@ window.app = new Vue({ return } - this.showSaveModal = true + this.plannedRoute.push(this.newJob) + this.newJob = { weight: 10 } // Reset job + }, - return axios.post(`/api/profiles/${this.selectedProfile}/saves/${this.selectedSave}/jobs`, this.newJob) + createRoute() { + this.showSaveModal = true + return axios.post(`/api/profiles/${this.selectedProfile}/saves/${this.selectedSave}/jobs`, this.plannedRoute) .then(() => { - this.showToast('Success', 'Job created', 'success') - this.newJob = { weight: 10 } // Reset job + this.showToast('Success', 'Route created', 'success') + this.plannedRoute = [] }) .catch(err => { - this.showToast('Uhoh…', 'Could not add job', 'danger') + this.showToast('Uhoh…', 'Could not add route', 'danger') console.error(err) }) }, @@ -307,6 +316,21 @@ window.app = new Vue({ } }, + removeJob(idx) { + if (idx < 0 || idx > this.plannedRoute.length - 1) { + return + } + + const newRoute = [] + for (const i in this.plannedRoute) { + if (parseInt(i) !== idx) { + newRoute.push(this.plannedRoute[i]) + } + } + + this.plannedRoute = newRoute + }, + saveIDToName(id) { if (id === 'quicksave') { return 'Quicksave' diff --git a/cmd/sii-editor/frontend/index.html b/cmd/sii-editor/frontend/index.html index 578eb01..b65dd67 100644 --- a/cmd/sii-editor/frontend/index.html +++ b/cmd/sii-editor/frontend/index.html @@ -161,7 +161,7 @@ - + - - Create Job - +
+ + Add Job + +
+ + + + + +
+ + + + {{ job.weight }}t {{ cargo[job.cargo_reference].name }} +
+ + From {{ companyNameFromReference(job.origin_reference) }}
+ To {{ companyNameFromReference(job.target_reference) }} +
+
+ + + +
+
+
+ +
+ + Create Jobs for Route + +
+
+
+ diff --git a/cmd/sii-editor/saves.go b/cmd/sii-editor/saves.go index f85e413..06ad044 100644 --- a/cmd/sii-editor/saves.go +++ b/cmd/sii-editor/saves.go @@ -3,6 +3,9 @@ package main import ( "os" "path" + "strconv" + "strings" + "time" "github.com/Luzifer/sii" "github.com/pkg/errors" @@ -144,6 +147,83 @@ func commSaveDetailsFromUnit(unit *sii.Unit) (out commSaveDetails, err error) { return out, nil } +func addJobToGame(game *sii.Unit, job commSaveJob, routePartID int) error { + // Get company + company := game.BlockByName(job.OriginReference).(*sii.Company) + // Get cargo + cargo := baseGameUnit.BlockByName(job.CargoReference).(*sii.CargoData) + + // Get trailer / truck from other jobs + var cTruck, cTV, cTD string + + for _, jp := range company.JobOffer { + j := jp.Resolve().(*sii.JobOfferData) + if j.CompanyTruck.IsNull() || j.TrailerVariant.IsNull() || j.TrailerDefinition.IsNull() { + continue + } + cTruck, cTV, cTD = j.CompanyTruck.Target, j.TrailerVariant.Target, j.TrailerDefinition.Target + break + } + + if cTruck == "" || cTV == "" || cTD == "" { + // The company did not have any valid job offers to steal from, lets search globally + for _, jb := range game.BlocksByClass("job_offer_data") { + j := jb.(*sii.JobOfferData) + if j.CompanyTruck.IsNull() || j.TrailerVariant.IsNull() || j.TrailerDefinition.IsNull() { + continue + } + cTruck, cTV, cTD = j.CompanyTruck.Target, j.TrailerVariant.Target, j.TrailerDefinition.Target + break + } + } + + if cTruck == "" || cTV == "" || cTD == "" { + // Even in global jobs there was no proper job, this savegame looks broken + return errors.New("Was not able to find suitable trailer definition") + } + + cargoCount := int64(job.Weight / cargo.Mass) + if cargoCount < 1 { + // Ensure cargo is transported (can happen if only small weight is + // requested and one unit weighs more than requested cargo) + cargoCount = 1 + } + + jobID := strings.Join([]string{ + "_nameless", + strconv.FormatInt(time.Now().Unix(), 16), + strconv.FormatInt(int64(routePartID), 16), + }, ".") + exTime := game.BlocksByClass("economy")[0].(*sii.Economy).GameTime + 300 // 300min = 5h + j := &sii.JobOfferData{ + // User requested job data + Target: strings.TrimPrefix(job.TargetReference, "company.volatile."), + ExpirationTime: &exTime, + Urgency: job.Urgency, + Cargo: sii.Ptr{Target: job.CargoReference}, + UnitsCount: cargoCount, + ShortestDistanceKM: job.Distance, + + // Some static data + FillRatio: 1, // Dunno but other jobs have it at 1, so keep for now + + // Dunno where this data comes from, it works without it and gets own ones + TrailerPlace: []sii.Placement{}, + + // Too lazy to implement, just steal it too + CompanyTruck: sii.Ptr{Target: cTruck}, + TrailerVariant: sii.Ptr{Target: cTV}, + TrailerDefinition: sii.Ptr{Target: cTD}, + } + j.Init("", jobID) + + // Add the new job to the game + game.Entries = append(game.Entries, j) + company.JobOffer = append([]sii.Ptr{{Target: j.Name()}}, company.JobOffer...) + + return nil +} + func fixPlayerTruck(unit *sii.Unit, fixType string) error { // In call cases we need the player as the starting point var player *sii.Player