Files
openttd/src/tbtr_template_vehicle_func.cpp
Jonathan G Rennison 6be2efc084 Update from KeldorKatarn branch
This approximately corresponds to 971ba4928a5c7c7916fea55d91a3b6dd5bba140c,
excluding the different virtual train build GUI, but including the basic
changes to the original train build GUI for multiplayer to work.

Fixup
2016-02-14 03:32:42 +00:00

526 lines
14 KiB
C++

// template_vehicle_func.cpp
#include "stdafx.h"
#include "window_gui.h"
#include "gfx_func.h"
#include "window_func.h"
#include "command_func.h"
#include "vehicle_gui.h"
#include "train.h"
#include "strings_func.h"
#include "vehicle_func.h"
#include "core/geometry_type.hpp"
#include "debug.h"
#include "table/sprites.h"
#include "table/strings.h"
#include "cargoaction.h"
#include "train.h"
#include "company_func.h"
#include "newgrf.h"
#include "spritecache.h"
#include "articulated_vehicles.h"
#include "autoreplace_func.h"
#include "depot_base.h"
#include "tbtr_template_vehicle.h"
#include "tbtr_template_vehicle_func.h"
#include <map>
#include <stdio.h>
Vehicle *vhead, *vtmp;
static const uint MAX_ARTICULATED_PARTS = 100;
// debugging printing functions for convenience, usually called from gdb
void pat() {
TemplateVehicle *tv;
FOR_ALL_TEMPLATES(tv) {
if ( tv->Prev() ) continue;
ptv(tv);
printf("__________\n");
}
}
void pav() {
Train *t;
FOR_ALL_TRAINS(t) {
if ( t->Previous() ) continue;
pvt(t);
printf("__________\n");
}
}
void ptv(TemplateVehicle* tv) {
if (!tv) return;
while (tv->Next() ) {
printf("eid:%3d st:%2d tv:%x next:%x cargo: %d cargo_sub: %d\n", tv->engine_type, tv->subtype, tv, tv->Next(), tv->cargo_type, tv->cargo_subtype);
tv = tv->Next();
}
printf("eid:%3d st:%2d tv:%x next:%x cargo: %d cargo_sub: %d\n", tv->engine_type, tv->subtype, tv, tv->Next(), tv->cargo_type, tv->cargo_subtype);
}
void pvt (const Train *printme) {
for ( const Train *tmp = printme; tmp; tmp=tmp->Next() ) {
if ( tmp->index <= 0 ) {
printf("train has weird index: %d %d %x\n", tmp->index, tmp->engine_type, (__int64)tmp);
return;
}
printf("eid:%3d index:%2d subtype:%2d vehstat: %d cargo_t: %d cargo_sub: %d ref:%x\n", tmp->engine_type, tmp->index, tmp->subtype, tmp->vehstatus, tmp->cargo_type, tmp->cargo_subtype, tmp);
}
}
void BuildTemplateGuiList(GUITemplateList *list, Scrollbar *vscroll, Owner oid, RailType railtype)
{
list->Clear();
const TemplateVehicle *tv;
FOR_ALL_TEMPLATES(tv) {
if (tv->owner == oid && (tv->IsPrimaryVehicle() || tv->IsFreeWagonChain()) && TemplateVehicleContainsEngineOfRailtype(tv, railtype))
*list->Append() = tv;
}
list->RebuildDone();
if (vscroll) vscroll->SetCount(list->Length());
}
Money CalculateOverallTemplateCost(const TemplateVehicle *tv)
{
Money val = 0;
for (; tv; tv = tv->Next())
val += (Engine::Get(tv->engine_type))->GetCost();
return val;
}
void DrawTemplate(const TemplateVehicle *tv, int left, int right, int y)
{
if ( !tv ) return;
const TemplateVehicle *t = tv;
int offset=left;
while (t) {
PaletteID pal = GetEnginePalette(t->engine_type, _current_company);
DrawSprite(t->cur_image, pal, offset, y+12);
offset += t->image_width;
t = t->Next();
}
}
// copy important stuff from the virtual vehicle to the template
inline void SetupTemplateVehicleFromVirtual(TemplateVehicle *tmp, TemplateVehicle *prev, Train *virt)
{
if (prev) {
prev->SetNext(tmp);
tmp->SetPrev(prev);
tmp->SetFirst(prev->First());
}
tmp->railtype = virt->railtype;
tmp->owner = virt->owner;
tmp->value = virt->value;
// set the subtype but also clear the virtual flag while doing it
tmp->subtype = virt->subtype & ~(1 << GVSF_VIRTUAL);
// set the cargo type and capacity
tmp->cargo_type = virt->cargo_type;
tmp->cargo_subtype = virt->cargo_subtype;
tmp->cargo_cap = virt->cargo_cap;
const GroundVehicleCache *gcache = virt->GetGroundVehicleCache();
tmp->max_speed = virt->GetDisplayMaxSpeed();
tmp->power = gcache->cached_power;
tmp->weight = gcache->cached_weight;
tmp->max_te = gcache->cached_max_te / 1000;
tmp->spritenum = virt->spritenum;
tmp->cur_image = virt->GetImage(DIR_W, EIT_PURCHASE);
Point *p = new Point();
tmp->image_width = virt->GetDisplayImageWidth(p);
}
// create a full TemplateVehicle based train according to a virtual train
TemplateVehicle* TemplateVehicleFromVirtualTrain(Train *virt)
{
if ( !virt )
return 0;
Train *init_virt = virt;
int len = CountVehiclesInChain(virt);
if ( !TemplateVehicle::CanAllocateItem(len) )
return 0;
TemplateVehicle *tmp, *prev=0;
for ( ; virt; virt=virt->Next() ) {
tmp = new TemplateVehicle(virt->engine_type);
SetupTemplateVehicleFromVirtual(tmp, prev, virt);
prev = tmp;
}
tmp->First()->SetRealLength(CeilDiv(init_virt->gcache.cached_total_length * 10, TILE_SIZE));
return tmp->First();
}
// return last in a chain (really last, so even a singular articulated part of a vehicle if the last one is artic)
inline TemplateVehicle* Last(TemplateVehicle *chain) {
if ( !chain ) return 0;
while ( chain->Next() ) chain = chain->Next();
return chain;
}
inline Train* Last(Train *chain) {
if ( !chain ) return 0;
while ( chain->GetNextUnit() ) chain = chain->GetNextUnit();
return chain;
}
// return: pointer to former vehicle
TemplateVehicle *DeleteTemplateVehicle(TemplateVehicle *todel)
{
if ( !todel )
return 0;
TemplateVehicle *cur = todel;
delete todel;
return cur;
}
// forward declaration, defined in train_cmd.cpp
CommandCost CmdSellRailWagon(DoCommandFlag, Vehicle*, uint16, uint32);
Train* DeleteVirtualTrain(Train *chain, Train *to_del) {
if ( chain != to_del ) {
CmdSellRailWagon(DC_EXEC, to_del, 0, 0);
return chain;
}
else {
chain = chain->GetNextUnit();
CmdSellRailWagon(DC_EXEC, to_del, 0, 0);
return chain;
}
}
// retrieve template vehicle from templatereplacement that belongs to the given group
TemplateVehicle* GetTemplateVehicleByGroupID(GroupID gid) {
TemplateReplacement *tr;
// first try to find a templatereplacement issued for the given groupid
FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
if ( tr->Group() == gid )
return TemplateVehicle::GetIfValid(tr->Template()); // there can be only one
}
// if that didn't work, try to find a templatereplacement for ALL_GROUP
if ( gid != ALL_GROUP )
FOR_ALL_TEMPLATE_REPLACEMENTS(tr) {
if ( tr->Group() == ALL_GROUP )
return TemplateVehicle::GetIfValid(tr->Template());
}
// if all failed, just return null
return 0;
}
/**
* Check a template consist whether it contains any engine of the given railtype
*/
bool TemplateVehicleContainsEngineOfRailtype(const TemplateVehicle *tv, RailType type)
{
/* For standard rail engines, allow only those */
if ( type == RAILTYPE_BEGIN || type == RAILTYPE_RAIL ) {
while ( tv ) {
if ( tv->railtype != type )
return false;
tv = tv->GetNextUnit();
}
return true;
}
/* For electrified rail engines, standard wagons or engines are allowed to be included */
while ( tv ) {
if ( tv->railtype == type )
return true;
tv = tv->GetNextUnit();
}
return false;
}
//helper
bool ChainContainsVehicle(Train *chain, Train *mem) {
for (; chain; chain=chain->Next())
if ( chain == mem )
return true;
return false;
}
// has O(n)
Train* ChainContainsEngine(EngineID eid, Train *chain) {
for (; chain; chain=chain->GetNextUnit())
if (chain->engine_type == eid)
return chain;
return 0;
}
// has O(n^2)
Train* DepotContainsEngine(TileIndex tile, EngineID eid, Train *not_in=0) {
Train *t;
FOR_ALL_TRAINS(t) {
// conditions: v is stopped in the given depot, has the right engine and if 'not_in' is given v must not be contained within 'not_in'
// if 'not_in' is NULL, no check is needed
if ( t->tile==tile
// If the veh belongs to a chain, wagons will not return true on IsStoppedInDepot(), only primary vehicles will
// in case of t not a primary veh, we demand it to be a free wagon to consider it for replacement
&& ((t->IsPrimaryVehicle() && t->IsStoppedInDepot()) || t->IsFreeWagon())
&& t->engine_type==eid
&& (not_in==0 || ChainContainsVehicle(not_in, t)==0))
return t;
}
return 0;
}
void CopyStatus(Train *from, Train *to) {
DoCommand(to->tile, from->group_id, to->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP);
to->cargo_type = from->cargo_type;
to->cargo_subtype = from->cargo_subtype;
// swap names
char *tmp = to->name;
to->name = from->name;
from->name = tmp;
/*if ( !from->name || !to->name ) {
int tmpind = from->index;
from->index = to->index;
to->index = tmpind;
}*/
}
void NeutralizeStatus(Train *t) {
DoCommand(t->tile, DEFAULT_GROUP, t->index, DC_EXEC, CMD_ADD_VEHICLE_GROUP);
DoCommand(0, t->index | CO_UNSHARE << 30, 0, DC_EXEC, CMD_CLONE_ORDER);
DoCommand(0, t->index, FreeUnitIDGenerator(VEH_TRAIN, t->owner).NextID(), DC_EXEC, CMD_SET_VEHICLE_UNIT_NUMBER);
DoCommand(0, t->index, 0, DC_EXEC, CMD_RENAME_VEHICLE, NULL);
}
bool TrainMatchesTemplate(const Train *t, TemplateVehicle *tv) {
while ( t && tv ) {
if ( t->engine_type != tv->engine_type )
return false;
t = t->GetNextUnit();
tv = tv->GetNextUnit();
}
if ( (t && !tv) || (!t && tv) )
return false;
return true;
}
bool TrainMatchesTemplateRefit(const Train *t, TemplateVehicle *tv)
{
if ( !tv->refit_as_template )
return true;
while ( t && tv ) {
if ( t->cargo_type != tv->cargo_type || t->cargo_subtype != tv->cargo_subtype )
return false;
t = t->GetNextUnit();
tv = tv->GetNextUnit();
}
return true;
}
void BreakUpRemainders(Train *t) {
while ( t ) {
Train *move;
if ( HasBit(t->subtype, GVSF_ENGINE) ) {
move = t;
t = t->Next();
DoCommand(move->tile, move->index, INVALID_VEHICLE, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
NeutralizeStatus( move );
}
else
t = t->Next();
}
}
short CountEnginesInChain(Train *t)
{
short count = 0;
for ( ; t; t=t->GetNextUnit() )
if ( HasBit(t->subtype, GVSF_ENGINE) )
count++;
return count;
}
int countOccurrencesInTrain(Train *t, EngineID eid) {
int count = 0;
Train *tmp = t;
for ( ; tmp; tmp=tmp->GetNextUnit() )
if ( tmp->engine_type == eid )
count++;
return count;
}
int countOccurrencesInTemplateVehicle(TemplateVehicle *contain, EngineID eid) {
int count = 0;
for ( ; contain; contain=contain->GetNextUnit() )
if ( contain->engine_type == eid )
count++;
return count;
}
int countOccurrencesInDepot(TileIndex tile, EngineID eid, Train *not_in=0) {
int count = 0;
Vehicle *v;
FOR_ALL_VEHICLES(v) {
// conditions: v is stopped in the given depot, has the right engine and if 'not_in' is given v must not be contained within 'not_in'
// if 'not_in' is NULL, no check is needed
if ( v->tile==tile && v->IsStoppedInDepot() && v->engine_type==eid &&
(not_in==0 || ChainContainsVehicle(not_in, (Train*)v)==0))
count++;
}
return count;
}
// basically does the same steps as CmdTemplateReplaceVehicle but without actually moving things around
CommandCost CalculateTemplateReplacementCost(Train *incoming) {
TileIndex tile = incoming->tile;
TemplateVehicle *tv = GetTemplateVehicleByGroupID(incoming->group_id);
CommandCost estimate(EXPENSES_NEW_VEHICLES);
// count for each different eid in the incoming train
std::map<EngineID, short> unique_eids;
for ( TemplateVehicle *tmp=tv; tmp; tmp=tmp->GetNextUnit() )
unique_eids[tmp->engine_type]++;
std::map<EngineID, short>::iterator it = unique_eids.begin();
for ( ; it!=unique_eids.end(); it++ ) {
it->second -= countOccurrencesInTrain(incoming, it->first);
it->second -= countOccurrencesInDepot(incoming->tile, it->first, incoming);
if ( it->second < 0 ) it->second = 0;
}
// get overall buying cost
for ( it=unique_eids.begin(); it!=unique_eids.end(); it++ ) {
for ( int j=0; j<it->second; j++ ) {
estimate.AddCost(DoCommand(tile, it->first, 0, DC_NONE, CMD_BUILD_VEHICLE));
}
}
return estimate;
}
// make sure the real train wagon has the right cargo
void CopyWagonStatus(TemplateVehicle *from, Train *to) {
to->cargo_type = from->cargo_type;
to->cargo_subtype = from->cargo_subtype;
}
int NumTrainsNeedTemplateReplacement(GroupID g_id, TemplateVehicle *tv)
{
int count = 0;
if ( !tv ) return count;
const Train *t;
FOR_ALL_TRAINS(t) {
if ( t->IsPrimaryVehicle() && t->group_id == g_id && (!TrainMatchesTemplate(t, tv) || !TrainMatchesTemplateRefit(t, tv)) )
count++;
}
return count;
}
// refit each vehicle in t as is in tv, assume t and tv contain the same types of vehicles
void CmdRefitTrainFromTemplate(Train *t, TemplateVehicle *tv, DoCommandFlag flags)
{
while ( t && tv ) {
// refit t as tv
uint32 cb = GetCmdRefitVeh(t);
DoCommand(t->tile, t->index, tv->cargo_type | tv->cargo_subtype << 8 | 1 << 16 | (1 << 5), flags, cb);
// next
t = t->GetNextUnit();
tv = tv->GetNextUnit();
}
}
/** using cmdtemplatereplacevehicle as test-function (i.e. with flag DC_NONE) is not a good idea as that function relies on
* actually moving vehicles around to work properly.
* We do this worst-cast test instead.
*/
CommandCost TestBuyAllTemplateVehiclesInChain(TemplateVehicle *tv, TileIndex tile)
{
CommandCost cost(EXPENSES_NEW_VEHICLES);
for ( ; tv; tv=tv->GetNextUnit() )
cost.AddCost( DoCommand(tile, tv->engine_type, 0, DC_NONE, CMD_BUILD_VEHICLE) );
return cost;
}
/** Transfer as much cargo from a given (single train) vehicle onto a chain of vehicles.
* I.e., iterate over the chain from head to tail and use all available cargo capacity (w.r.t. cargo type of course)
* to store the cargo from the given single vehicle.
* @param old_veh: ptr to the single vehicle, which's cargo shall be moved
* @param new_head: ptr to the head of the chain, which shall obtain old_veh's cargo
* @return: amount of moved cargo TODO
*/
void TransferCargoForTrain(Train *old_veh, Train *new_head)
{
assert(new_head->IsPrimaryVehicle());
CargoID _cargo_type = old_veh->cargo_type;
byte _cargo_subtype = old_veh->cargo_subtype;
// how much cargo has to be moved (if possible)
uint remainingAmount = old_veh->cargo.TotalCount();
// each vehicle in the new chain shall be given as much of the old cargo as possible, until none is left
for (Train *tmp=new_head; tmp!=NULL && remainingAmount>0; tmp=tmp->GetNextUnit())
{
if (tmp->cargo_type == _cargo_type && tmp->cargo_subtype == _cargo_subtype)
{
// calculate the free space for new cargo on the current vehicle
uint curCap = tmp->cargo_cap - tmp->cargo.TotalCount();
uint moveAmount = min(remainingAmount, curCap);
// move (parts of) the old vehicle's cargo onto the current vehicle of the new chain
if (moveAmount > 0)
{
old_veh->cargo.Shift(moveAmount, &tmp->cargo);
remainingAmount -= moveAmount;
}
}
}
// TODO: needs to be implemented, too
// // from autoreplace_cmd.cpp : 121
/* Any left-overs will be thrown away, but not their feeder share. */
//if (src->cargo_cap < src->cargo.TotalCount()) src->cargo.Truncate(src->cargo.TotalCount() - src->cargo_cap);
/* Update train weight etc., the old vehicle will be sold anyway */
new_head->ConsistChanged(CCF_LOADUNLOAD);
}