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) {
|
||||
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"))
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -161,7 +161,7 @@
|
|||
</b-col>
|
||||
|
||||
<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-group
|
||||
label="Source Company"
|
||||
|
@ -214,15 +214,57 @@
|
|||
></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-button
|
||||
type="submit"
|
||||
>
|
||||
Create Job
|
||||
</b-button>
|
||||
<div class="text-center">
|
||||
<b-btn
|
||||
type="submit"
|
||||
>
|
||||
Add Job
|
||||
</b-btn>
|
||||
</div>
|
||||
|
||||
</b-form>
|
||||
</b-card>
|
||||
</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-col>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue