mirror of
https://github.com/Luzifer/sii.git
synced 2024-10-18 05:14:19 +00:00
Multi stop route planning (#2)
* Add support for multi-job creation Signed-off-by: Knut Ahlers <knut@ahlers.me> * Fix: Ensure at least one unit of cargo is transported Signed-off-by: Knut Ahlers <knut@ahlers.me> * Multi-stop route planning Signed-off-by: Knut Ahlers <knut@ahlers.me> * Linter + fix of weird char deletion Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
ec468c07a8
commit
b515c32f98
4 changed files with 181 additions and 85 deletions
|
@ -30,11 +30,11 @@ func init() {
|
||||||
|
|
||||||
func handleAddJob(w http.ResponseWriter, r *http.Request) {
|
func handleAddJob(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
job commSaveJob
|
jobs []commSaveJob
|
||||||
vars = mux.Vars(r)
|
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"))
|
apiGenericError(w, http.StatusBadRequest, errors.Wrap(err, "Unable to decode input data"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ func handleAddJob(w http.ResponseWriter, r *http.Request) {
|
||||||
info.SaveName = storeSaveName
|
info.SaveName = storeSaveName
|
||||||
info.FileTime = time.Now().Unix()
|
info.FileTime = time.Now().Unix()
|
||||||
|
|
||||||
|
for i, job := range jobs {
|
||||||
// Set urgency if it isn't
|
// Set urgency if it isn't
|
||||||
if job.Urgency == nil {
|
if job.Urgency == nil {
|
||||||
u := int64(0)
|
u := int64(0)
|
||||||
|
@ -67,63 +68,12 @@ func handleAddJob(w http.ResponseWriter, r *http.Request) {
|
||||||
job.Distance = 100 // If the user did not provide distance use 100km as a default
|
job.Distance = 100 // If the user did not provide distance use 100km as a default
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get company
|
if err = addJobToGame(game, job, i); err != nil {
|
||||||
company := game.BlockByName(job.OriginReference).(*sii.Company)
|
apiGenericError(w, http.StatusInternalServerError, errors.Wrap(err, "Unable to add job"))
|
||||||
// Get cargo
|
return
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Write the save-game
|
||||||
if err = storeSave(vars["profileID"], storeSaveFolder, game, info); err != nil {
|
if err = storeSave(vars["profileID"], storeSaveFolder, game, info); err != nil {
|
||||||
apiGenericError(w, http.StatusInternalServerError, errors.Wrap(err, "Unable to store save"))
|
apiGenericError(w, http.StatusInternalServerError, errors.Wrap(err, "Unable to store save"))
|
||||||
|
|
|
@ -26,7 +26,7 @@ window.app = new Vue({
|
||||||
for (const ref in this.companies) {
|
for (const ref in this.companies) {
|
||||||
result.push({
|
result.push({
|
||||||
value: ref,
|
value: ref,
|
||||||
text: `${this.companies[ref].city}, ${this.companies[ref].name}`,
|
text: this.companyNameFromReference(ref),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +153,7 @@ window.app = new Vue({
|
||||||
companies: {},
|
companies: {},
|
||||||
jobs: [],
|
jobs: [],
|
||||||
newJob: { weight: 10 },
|
newJob: { weight: 10 },
|
||||||
|
plannedRoute: [],
|
||||||
profiles: {},
|
profiles: {},
|
||||||
save: null,
|
save: null,
|
||||||
saveLoading: false,
|
saveLoading: false,
|
||||||
|
@ -177,6 +178,10 @@ window.app = new Vue({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
companyNameFromReference(ref) {
|
||||||
|
return `${this.companies[ref].city}, ${this.companies[ref].name}`
|
||||||
|
},
|
||||||
|
|
||||||
createJob() {
|
createJob() {
|
||||||
if (!this.companies[this.newJob.origin_reference]) {
|
if (!this.companies[this.newJob.origin_reference]) {
|
||||||
this.showToast('Uhm…', 'Source Company does not exist', 'danger')
|
this.showToast('Uhm…', 'Source Company does not exist', 'danger')
|
||||||
|
@ -199,15 +204,19 @@ window.app = new Vue({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showSaveModal = true
|
this.plannedRoute.push(this.newJob)
|
||||||
|
|
||||||
return axios.post(`/api/profiles/${this.selectedProfile}/saves/${this.selectedSave}/jobs`, this.newJob)
|
|
||||||
.then(() => {
|
|
||||||
this.showToast('Success', 'Job created', 'success')
|
|
||||||
this.newJob = { weight: 10 } // Reset job
|
this.newJob = { weight: 10 } // Reset job
|
||||||
|
},
|
||||||
|
|
||||||
|
createRoute() {
|
||||||
|
this.showSaveModal = true
|
||||||
|
return axios.post(`/api/profiles/${this.selectedProfile}/saves/${this.selectedSave}/jobs`, this.plannedRoute)
|
||||||
|
.then(() => {
|
||||||
|
this.showToast('Success', 'Route created', 'success')
|
||||||
|
this.plannedRoute = []
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.showToast('Uhoh…', 'Could not add job', 'danger')
|
this.showToast('Uhoh…', 'Could not add route', 'danger')
|
||||||
console.error(err)
|
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) {
|
saveIDToName(id) {
|
||||||
if (id === 'quicksave') {
|
if (id === 'quicksave') {
|
||||||
return 'Quicksave'
|
return 'Quicksave'
|
||||||
|
|
|
@ -161,7 +161,7 @@
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|
||||||
<b-col cols="12" xl="6" class="mb-3">
|
<b-col cols="12" xl="6" class="mb-3">
|
||||||
<b-card header="Create Job">
|
<b-card header="Add Job to Route">
|
||||||
<b-form @submit.prevent="createJob">
|
<b-form @submit.prevent="createJob">
|
||||||
<b-form-group
|
<b-form-group
|
||||||
label="Source Company"
|
label="Source Company"
|
||||||
|
@ -214,15 +214,57 @@
|
||||||
></b-form-input>
|
></b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
|
||||||
<b-button
|
<div class="text-center">
|
||||||
|
<b-btn
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Create Job
|
Add Job
|
||||||
</b-button>
|
</b-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
</b-form>
|
</b-form>
|
||||||
</b-card>
|
</b-card>
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|
||||||
|
<b-col cols="12" xl="6" class="mb-3">
|
||||||
|
<b-card header="Planned route">
|
||||||
|
<b-list-group class="mb-2" flush v-if="plannedRoute.length > 0">
|
||||||
|
<b-list-group-item
|
||||||
|
v-for="(job, idx) in plannedRoute"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<div class="d-flex w-100 justify-content-between">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<i class="fa fa-fw fa-box mr-1"></i>
|
||||||
|
<strong>{{ job.weight }}t {{ cargo[job.cargo_reference].name }}</strong>
|
||||||
|
</span><br>
|
||||||
|
<small>
|
||||||
|
From <strong>{{ companyNameFromReference(job.origin_reference) }}</strong><br>
|
||||||
|
To <strong>{{ companyNameFromReference(job.target_reference) }}</strong>
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
<b-btn
|
||||||
|
@click="removeJob(idx)"
|
||||||
|
variant="danger"
|
||||||
|
>
|
||||||
|
<i class="fa fa-fw fa-trash"></i>
|
||||||
|
</b-btn>
|
||||||
|
</div>
|
||||||
|
</b-list-group-item>
|
||||||
|
</b-list-group>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<b-btn
|
||||||
|
@click="createRoute"
|
||||||
|
:disabled="plannedRoute.length == 0"
|
||||||
|
>
|
||||||
|
Create Jobs for Route
|
||||||
|
</b-btn>
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-col>
|
||||||
|
|
||||||
</b-row>
|
</b-row>
|
||||||
|
|
||||||
</b-col>
|
</b-col>
|
||||||
|
|
|
@ -3,6 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Luzifer/sii"
|
"github.com/Luzifer/sii"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -144,6 +147,83 @@ func commSaveDetailsFromUnit(unit *sii.Unit) (out commSaveDetails, err error) {
|
||||||
return out, nil
|
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 {
|
func fixPlayerTruck(unit *sii.Unit, fixType string) error {
|
||||||
// In call cases we need the player as the starting point
|
// In call cases we need the player as the starting point
|
||||||
var player *sii.Player
|
var player *sii.Player
|
||||||
|
|
Loading…
Reference in a new issue