mirror of
https://github.com/Luzifer/sii.git
synced 2024-12-21 08:21:16 +00:00
329 lines
8.6 KiB
Go
329 lines
8.6 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/Luzifer/sii"
|
|
)
|
|
|
|
const jobExpiryPerSegment = 4 * 24 * 60 // 4d * 24h * 60m
|
|
|
|
const (
|
|
fixAll = "all"
|
|
fixCargoOnly = "cargo"
|
|
fixTrailerOnly = "trailer"
|
|
fixTruckOnly = "truck"
|
|
)
|
|
|
|
type commSaveDetails struct {
|
|
GameTime int64 `json:"game_time"`
|
|
|
|
ExperiencePoints int64 `json:"experience_points"`
|
|
Money int64 `json:"money"`
|
|
|
|
CargoDamage float32 `json:"cargo_damage"`
|
|
TruckWear float32 `json:"truck_wear"`
|
|
TrailerAttached bool `json:"trailer_attached"`
|
|
TrailerWear float32 `json:"trailer_wear"`
|
|
|
|
AttachedTrailer string `json:"attached_trailer"`
|
|
OwnedTrailers map[string]string `json:"owned_trailers"`
|
|
|
|
CurrentJob *commSaveJob `json:"current_job"`
|
|
}
|
|
|
|
type commSaveJob struct {
|
|
OriginReference string `json:"origin_reference"`
|
|
TargetReference string `json:"target_reference"`
|
|
CargoReference string `json:"cargo_reference"`
|
|
TrailerReference string `json:"trailer_reference"`
|
|
Distance int64 `json:"distance"`
|
|
Urgency *int64 `json:"urgency,omitempty"`
|
|
Weight float32 `json:"weight,omitempty"`
|
|
Expires int64 `json:"expires,omitempty"`
|
|
}
|
|
|
|
func commSaveDetailsFromUnit(unit *sii.Unit) (out commSaveDetails, err error) {
|
|
var economy *sii.Economy
|
|
|
|
for _, b := range unit.BlocksByClass("economy") {
|
|
if v, ok := b.(*sii.Economy); ok {
|
|
economy = v
|
|
}
|
|
}
|
|
|
|
if economy == nil {
|
|
return out, errors.New("Found no economy object")
|
|
}
|
|
|
|
var (
|
|
bank *sii.Bank
|
|
job *sii.PlayerJob
|
|
player *sii.Player
|
|
truck *sii.Vehicle
|
|
trailer *sii.Trailer
|
|
)
|
|
|
|
bank = economy.Bank.Resolve().(*sii.Bank)
|
|
player = economy.Player.Resolve().(*sii.Player)
|
|
|
|
out.ExperiencePoints = economy.ExperiencePoints
|
|
out.GameTime = economy.GameTime
|
|
out.Money = bank.MoneyAccount
|
|
out.TrailerAttached = player.AssignedTrailerConnected
|
|
|
|
if v, ok := player.CurrentJob.Resolve().(*sii.PlayerJob); ok {
|
|
job = v
|
|
}
|
|
|
|
if v, ok := player.AssignedTruck.Resolve().(*sii.Vehicle); ok {
|
|
truck = v
|
|
}
|
|
|
|
if v, ok := player.AssignedTrailer.Resolve().(*sii.Trailer); ok {
|
|
out.AttachedTrailer = player.AssignedTrailer.Target
|
|
trailer = v
|
|
}
|
|
|
|
if truck != nil {
|
|
for _, pb := range truck.Accessories {
|
|
var wear float32
|
|
if v, ok := pb.Resolve().(*sii.VehicleAccessory); ok {
|
|
wear = v.Wear
|
|
}
|
|
|
|
if v, ok := pb.Resolve().(*sii.VehicleWheelAccessory); ok {
|
|
wear = v.Wear
|
|
}
|
|
|
|
if wear > out.TruckWear {
|
|
out.TruckWear = wear
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(player.Trailers) > 0 {
|
|
out.OwnedTrailers = map[string]string{}
|
|
for _, tp := range player.Trailers {
|
|
if t, ok := tp.Resolve().(*sii.Trailer); ok {
|
|
out.OwnedTrailers[t.Name()] = t.CleanedLicensePlate()
|
|
}
|
|
}
|
|
}
|
|
|
|
if trailer != nil {
|
|
for _, pb := range trailer.Accessories {
|
|
var wear float32
|
|
if v, ok := pb.Resolve().(*sii.VehicleAccessory); ok {
|
|
wear = v.Wear
|
|
}
|
|
|
|
if v, ok := pb.Resolve().(*sii.VehicleWheelAccessory); ok {
|
|
wear = v.Wear
|
|
}
|
|
|
|
if wear > out.TrailerWear {
|
|
out.TrailerWear = wear
|
|
}
|
|
}
|
|
|
|
out.CargoDamage = trailer.CargoDamage
|
|
}
|
|
|
|
if job != nil {
|
|
out.CurrentJob = &commSaveJob{
|
|
OriginReference: job.SourceCompany.Target,
|
|
TargetReference: job.TargetCompany.Target,
|
|
CargoReference: job.Cargo.Target,
|
|
TrailerReference: job.CompanyTrailer.Target,
|
|
Distance: job.PlannedDistanceKM,
|
|
Urgency: job.Urgency,
|
|
}
|
|
}
|
|
|
|
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 + jobExpiryPerSegment*int64(routePartID+1)
|
|
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
|
|
|
|
for _, b := range unit.Entries {
|
|
if v, ok := b.(*sii.Player); ok {
|
|
player = v
|
|
break
|
|
}
|
|
}
|
|
|
|
if player == nil {
|
|
return errors.New("Found no player object")
|
|
}
|
|
|
|
// Fix truck
|
|
if fixType == fixAll || fixType == fixTruckOnly {
|
|
truck := player.AssignedTruck.Resolve().(*sii.Vehicle)
|
|
for _, pb := range truck.Accessories {
|
|
if v, ok := pb.Resolve().(*sii.VehicleAccessory); ok {
|
|
v.Wear = 0
|
|
}
|
|
|
|
if v, ok := pb.Resolve().(*sii.VehicleWheelAccessory); ok {
|
|
v.Wear = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix trailer
|
|
if (fixType == fixAll || fixType == fixTrailerOnly) && player.AssignedTrailer.Resolve() != nil {
|
|
trailer := player.AssignedTrailer.Resolve().(*sii.Trailer)
|
|
for _, pb := range trailer.Accessories {
|
|
if v, ok := pb.Resolve().(*sii.VehicleAccessory); ok {
|
|
v.Wear = 0
|
|
}
|
|
|
|
if v, ok := pb.Resolve().(*sii.VehicleWheelAccessory); ok {
|
|
v.Wear = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix cargo
|
|
if (fixType == fixAll || fixType == fixCargoOnly) && player.AssignedTrailer.Resolve() != nil {
|
|
trailer := player.AssignedTrailer.Resolve().(*sii.Trailer)
|
|
trailer.CargoDamage = 0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// loadSave reads game- and info- unit and returns them unmodified
|
|
func loadSave(profile, save string) (*sii.Unit, *sii.SaveContainer, error) {
|
|
var (
|
|
saveInfoPath = getSaveFilePath(profile, save, "info.sii")
|
|
saveUnitPath = getSaveFilePath(profile, save, "game.sii")
|
|
)
|
|
|
|
infoUnit, err := sii.ReadUnitFile(saveInfoPath)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "Unable to load save-info")
|
|
}
|
|
|
|
var info *sii.SaveContainer
|
|
for _, b := range infoUnit.Entries {
|
|
if v, ok := b.(*sii.SaveContainer); ok {
|
|
info = v
|
|
}
|
|
}
|
|
|
|
gameUnit, err := sii.ReadUnitFile(saveUnitPath)
|
|
if err != nil {
|
|
return nil, info, errors.Wrap(err, "Unable to load save-unit")
|
|
}
|
|
|
|
return gameUnit, info, nil
|
|
}
|
|
|
|
// storeSave writes game- and info- unit without checking for existance!
|
|
func storeSave(profile, save string, unit *sii.Unit, info *sii.SaveContainer) error {
|
|
var (
|
|
saveInfoPath = getSaveFilePath(profile, save, "info.sii")
|
|
saveUnitPath = getSaveFilePath(profile, save, "game.sii")
|
|
)
|
|
|
|
if err := os.MkdirAll(path.Dir(saveInfoPath), 0700); err != nil {
|
|
return errors.Wrap(err, "Unable to create save-dir")
|
|
}
|
|
|
|
if err := sii.WriteUnitFile(saveInfoPath, &sii.Unit{Entries: []sii.Block{info}}); err != nil {
|
|
return errors.Wrap(err, "Unable to write info unit")
|
|
}
|
|
|
|
if err := sii.WriteUnitFile(saveUnitPath, unit); err != nil {
|
|
return errors.Wrap(err, "Unable to write game unit")
|
|
}
|
|
|
|
return nil
|
|
}
|