1
0
Fork 0
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:
Knut Ahlers 2020-01-04 15:33:43 +01:00 committed by GitHub
parent ec468c07a8
commit b515c32f98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 85 deletions

View file

@ -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,6 +48,7 @@ func handleAddJob(w http.ResponseWriter, r *http.Request) {
info.SaveName = storeSaveName
info.FileTime = time.Now().Unix()
for i, job := range jobs {
// Set urgency if it isn't
if job.Urgency == nil {
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
}
// 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 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"))

View file

@ -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
return axios.post(`/api/profiles/${this.selectedProfile}/saves/${this.selectedSave}/jobs`, this.newJob)
.then(() => {
this.showToast('Success', 'Job created', 'success')
this.plannedRoute.push(this.newJob)
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 => {
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'

View file

@ -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
<div class="text-center">
<b-btn
type="submit"
>
Create Job
</b-button>
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>

View file

@ -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