Changeset - r26791:8e217aa7f11b
[Not reviewed]
master
0 5 0
SamuXarick - 23 months ago 2023-01-22 13:14:02
43006711+SamuXarick@users.noreply.github.com
Fix 3c047b1: AIGroup.GetProfitLastYear could get values different than those displayed in GUI (#10227)

* Change: Store "all time" and "since minimum age" last year profits on groups

* Fix: Update last year profit for groups when copying vehicle statistics on autoreplace

* Codechange: Refactor profit last year

* Change: Rename some group related items for clarity

* Change: Reorder the fields in GroupStatistics

That way less memory gets wasted.
5 files changed with 55 insertions and 34 deletions:
0 comments (0 inline, 0 general)
src/autoreplace_cmd.cpp
Show inline comments
 
@@ -413,24 +413,25 @@ static CommandCost CopyHeadSpecificThing
 
		/* Start the vehicle, might be denied by certain things */
 
		assert((new_head->vehstatus & VS_STOPPED) != 0);
 
		cost.AddCost(DoCmdStartStopVehicle(new_head, true));
 

	
 
		/* Stop the vehicle again, but do not care about evil newgrfs allowing starting but not stopping :p */
 
		if (cost.Succeeded()) cost.AddCost(DoCmdStartStopVehicle(new_head, false));
 
	}
 

	
 
	/* Last do those things which do never fail (resp. we do not care about), but which are not undo-able */
 
	if (cost.Succeeded() && old_head != new_head && (flags & DC_EXEC) != 0) {
 
		/* Copy other things which cannot be copied by a command and which shall not stay resetted from the build vehicle command */
 
		new_head->CopyVehicleConfigAndStatistics(old_head);
 
		GroupStatistics::AddProfitLastYear(new_head);
 

	
 
		/* Switch vehicle windows/news to the new vehicle, so they are not closed/deleted when the old vehicle is sold */
 
		ChangeVehicleViewports(old_head->index, new_head->index);
 
		ChangeVehicleViewWindow(old_head->index, new_head->index);
 
		ChangeVehicleNews(old_head->index, new_head->index);
 
	}
 

	
 
	return cost;
 
}
 

	
 
/**
 
 * Replace a single unit in a free wagon chain
src/group.h
Show inline comments
 
@@ -14,57 +14,59 @@
 
#include "core/pool_type.hpp"
 
#include "company_type.h"
 
#include "vehicle_type.h"
 
#include "engine_type.h"
 
#include "livery.h"
 
#include <string>
 

	
 
typedef Pool<Group, GroupID, 16, 64000> GroupPool;
 
extern GroupPool _group_pool; ///< Pool of groups.
 

	
 
/** Statistics and caches on the vehicles in a group. */
 
struct GroupStatistics {
 
	uint16 num_vehicle;                     ///< Number of vehicles.
 
	Money profit_last_year;                 ///< Sum of profits for all vehicles.
 
	Money profit_last_year_min_age;         ///< Sum of profits for vehicles considered for profit statistics.
 
	uint16 *num_engines;                    ///< Caches the number of engines of each type the company owns.
 

	
 
	uint16 num_vehicle;                     ///< Number of vehicles.
 
	uint16 num_vehicle_min_age;             ///< Number of vehicles considered for profit statistics;
 
	bool autoreplace_defined;               ///< Are any autoreplace rules set?
 
	bool autoreplace_finished;              ///< Have all autoreplacement finished?
 

	
 
	uint16 num_profit_vehicle;              ///< Number of vehicles considered for profit statistics;
 
	Money profit_last_year;                 ///< Sum of profits for all vehicles.
 

	
 
	GroupStatistics();
 
	~GroupStatistics();
 

	
 
	void Clear();
 

	
 
	void ClearProfits()
 
	{
 
		this->num_profit_vehicle = 0;
 
		this->profit_last_year = 0;
 

	
 
		this->num_vehicle_min_age = 0;
 
		this->profit_last_year_min_age = 0;
 
	}
 

	
 
	void ClearAutoreplace()
 
	{
 
		this->autoreplace_defined = false;
 
		this->autoreplace_finished = false;
 
	}
 

	
 
	static GroupStatistics &Get(CompanyID company, GroupID id_g, VehicleType type);
 
	static GroupStatistics &Get(const Vehicle *v);
 
	static GroupStatistics &GetAllGroup(const Vehicle *v);
 

	
 
	static void CountVehicle(const Vehicle *v, int delta);
 
	static void CountEngine(const Vehicle *v, int delta);
 
	static void VehicleReachedProfitAge(const Vehicle *v);
 
	static void AddProfitLastYear(const Vehicle *v);
 
	static void VehicleReachedMinAge(const Vehicle *v);
 

	
 
	static void UpdateProfits();
 
	static void UpdateAfterLoad();
 
	static void UpdateAutoreplace(CompanyID company);
 
};
 

	
 
enum GroupFlags : uint8 {
 
	GF_REPLACE_PROTECTION,    ///< If set to true, the global autoreplace has no effect on the group
 
	GF_REPLACE_WAGON_REMOVAL, ///< If set, autoreplace will perform wagon removal on vehicles in this group.
 
	GF_END,
 
};
 

	
 
@@ -95,22 +97,22 @@ static inline bool IsDefaultGroupID(Grou
 
 * Checks if a GroupID stands for all vehicles of a company
 
 * @param id_g The GroupID to check
 
 * @return true is id_g is identical to ALL_GROUP
 
 */
 
static inline bool IsAllGroupID(GroupID id_g)
 
{
 
	return id_g == ALL_GROUP;
 
}
 

	
 

	
 
uint GetGroupNumEngines(CompanyID company, GroupID id_g, EngineID id_e);
 
uint GetGroupNumVehicle(CompanyID company, GroupID id_g, VehicleType type);
 
uint GetGroupNumProfitVehicle(CompanyID company, GroupID id_g, VehicleType type);
 
Money GetGroupProfitLastYear(CompanyID company, GroupID id_g, VehicleType type);
 
uint GetGroupNumVehicleMinAge(CompanyID company, GroupID id_g, VehicleType type);
 
Money GetGroupProfitLastYearMinAge(CompanyID company, GroupID id_g, VehicleType type);
 

	
 
void SetTrainGroupID(Train *v, GroupID grp);
 
void UpdateTrainGroupID(Train *v);
 
void RemoveVehicleFromGroup(const Vehicle *v);
 
void RemoveAllGroupsForCompany(const CompanyID company);
 
bool GroupIsInGroup(GroupID search, GroupID group);
 

	
 
#endif /* GROUP_H */
src/group_cmd.cpp
Show inline comments
 
@@ -34,26 +34,27 @@ GroupStatistics::GroupStatistics()
 

	
 
GroupStatistics::~GroupStatistics()
 
{
 
	free(this->num_engines);
 
}
 

	
 
/**
 
 * Clear all caches.
 
 */
 
void GroupStatistics::Clear()
 
{
 
	this->num_vehicle = 0;
 
	this->num_profit_vehicle = 0;
 
	this->profit_last_year = 0;
 
	this->num_vehicle_min_age = 0;
 
	this->profit_last_year_min_age = 0;
 

	
 
	/* This is also called when NewGRF change. So the number of engines might have changed. Reallocate. */
 
	free(this->num_engines);
 
	this->num_engines = CallocT<uint16>(Engine::GetPoolSize());
 
}
 

	
 
/**
 
 * Returns the GroupStatistics for a specific group.
 
 * @param company Owner of the group.
 
 * @param id_g    GroupID of the group.
 
 * @param type    VehicleType of the vehicles in the group.
 
 * @return Statistics for the group.
 
@@ -127,80 +128,97 @@ void GroupStatistics::Clear()
 
 * Update num_vehicle when adding or removing a vehicle.
 
 * @param v Vehicle to count.
 
 * @param delta +1 to add, -1 to remove.
 
 */
 
/* static */ void GroupStatistics::CountVehicle(const Vehicle *v, int delta)
 
{
 
	assert(delta == 1 || delta == -1);
 

	
 
	GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
 
	GroupStatistics &stats = GroupStatistics::Get(v);
 

	
 
	stats_all.num_vehicle += delta;
 
	stats_all.profit_last_year += v->GetDisplayProfitLastYear() * delta;
 
	stats.num_vehicle += delta;
 
	stats.profit_last_year += v->GetDisplayProfitLastYear() * delta;
 

	
 
	if (v->age > VEHICLE_PROFIT_MIN_AGE) {
 
		stats_all.num_profit_vehicle += delta;
 
		stats_all.profit_last_year += v->GetDisplayProfitLastYear() * delta;
 
		stats.num_profit_vehicle += delta;
 
		stats.profit_last_year += v->GetDisplayProfitLastYear() * delta;
 
		stats_all.num_vehicle_min_age += delta;
 
		stats_all.profit_last_year_min_age += v->GetDisplayProfitLastYear() * delta;
 
		stats.num_vehicle_min_age += delta;
 
		stats.profit_last_year_min_age += v->GetDisplayProfitLastYear() * delta;
 
	}
 
}
 

	
 
/**
 
 * Update num_engines when adding/removing an engine.
 
 * @param v Engine to count.
 
 * @param delta +1 to add, -1 to remove.
 
 */
 
/* static */ void GroupStatistics::CountEngine(const Vehicle *v, int delta)
 
{
 
	assert(delta == 1 || delta == -1);
 
	GroupStatistics::GetAllGroup(v).num_engines[v->engine_type] += delta;
 
	GroupStatistics::Get(v).num_engines[v->engine_type] += delta;
 
}
 

	
 
/**
 
 * Add a vehicle to the profit sum of its group.
 
 * Add a vehicle's last year profit to the profit sum of its group.
 
 */
 
/* static */ void GroupStatistics::VehicleReachedProfitAge(const Vehicle *v)
 
/* static */ void GroupStatistics::AddProfitLastYear(const Vehicle *v)
 
{
 
	GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
 
	GroupStatistics &stats = GroupStatistics::Get(v);
 

	
 
	stats_all.num_profit_vehicle++;
 
	stats_all.profit_last_year += v->GetDisplayProfitLastYear();
 
	stats.num_profit_vehicle++;
 
	stats.profit_last_year += v->GetDisplayProfitLastYear();
 
}
 

	
 
/**
 
 * Add a vehicle to the profit sum of its group.
 
 */
 
/* static */ void GroupStatistics::VehicleReachedMinAge(const Vehicle *v)
 
{
 
	GroupStatistics &stats_all = GroupStatistics::GetAllGroup(v);
 
	GroupStatistics &stats = GroupStatistics::Get(v);
 

	
 
	stats_all.num_vehicle_min_age++;
 
	stats_all.profit_last_year_min_age += v->GetDisplayProfitLastYear();
 
	stats.num_vehicle_min_age++;
 
	stats.profit_last_year_min_age += v->GetDisplayProfitLastYear();
 
}
 

	
 
/**
 
 * Recompute the profits for all groups.
 
 */
 
/* static */ void GroupStatistics::UpdateProfits()
 
{
 
	/* Set up the engine count for all companies */
 
	for (Company *c : Company::Iterate()) {
 
		for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
 
			c->group_all[type].ClearProfits();
 
			c->group_default[type].ClearProfits();
 
		}
 
	}
 

	
 
	/* Recalculate */
 
	for (Group *g : Group::Iterate()) {
 
		g->statistics.ClearProfits();
 
	}
 

	
 
	for (const Vehicle *v : Vehicle::Iterate()) {
 
		if (v->IsPrimaryVehicle() && v->age > VEHICLE_PROFIT_MIN_AGE) GroupStatistics::VehicleReachedProfitAge(v);
 
		if (v->IsPrimaryVehicle()) {
 
			GroupStatistics::AddProfitLastYear(v);
 
			if (v->age > VEHICLE_PROFIT_MIN_AGE) GroupStatistics::VehicleReachedMinAge(v);
 
		}
 
	}
 
}
 

	
 
/**
 
 * Update autoreplace_defined and autoreplace_finished of all statistics of a company.
 
 * @param company Company to update statistics for.
 
 */
 
/* static */ void GroupStatistics::UpdateAutoreplace(CompanyID company)
 
{
 
	/* Set up the engine count for all companies */
 
	Company *c = Company::Get(company);
 
	for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
 
@@ -780,48 +798,48 @@ uint GetGroupNumVehicle(CompanyID compan
 
	}
 
	return count + GroupStatistics::Get(company, id_g, type).num_vehicle;
 
}
 

	
 
/**
 
 * Get the number of vehicles above profit minimum age in the group with GroupID
 
 * id_g and its sub-groups.
 
 * @param company The company the group belongs to
 
 * @param id_g The GroupID of the group used
 
 * @param type The vehicle type of the group
 
 * @return The number of vehicles above profit minimum age in the group
 
 */
 
uint GetGroupNumProfitVehicle(CompanyID company, GroupID id_g, VehicleType type)
 
uint GetGroupNumVehicleMinAge(CompanyID company, GroupID id_g, VehicleType type)
 
{
 
	uint count = 0;
 
	for (const Group *g : Group::Iterate()) {
 
		if (g->parent == id_g) count += GetGroupNumProfitVehicle(company, g->index, type);
 
		if (g->parent == id_g) count += GetGroupNumVehicleMinAge(company, g->index, type);
 
	}
 
	return count + GroupStatistics::Get(company, id_g, type).num_profit_vehicle;
 
	return count + GroupStatistics::Get(company, id_g, type).num_vehicle_min_age;
 
}
 

	
 
/**
 
 * Get last year's profit for the group with GroupID
 
 * id_g and its sub-groups.
 
 * Get last year's profit of vehicles above minimum age
 
 * for the group with GroupID id_g and its sub-groups.
 
 * @param company The company the group belongs to
 
 * @param id_g The GroupID of the group used
 
 * @param type The vehicle type of the group
 
 * @return Last year's profit for the group
 
 * @return Last year's profit of vehicles above minimum age for the group
 
 */
 
Money GetGroupProfitLastYear(CompanyID company, GroupID id_g, VehicleType type)
 
Money GetGroupProfitLastYearMinAge(CompanyID company, GroupID id_g, VehicleType type)
 
{
 
	Money sum = 0;
 
	for (const Group *g : Group::Iterate()) {
 
		if (g->parent == id_g) sum += GetGroupProfitLastYear(company, g->index, type);
 
		if (g->parent == id_g) sum += GetGroupProfitLastYearMinAge(company, g->index, type);
 
	}
 
	return sum + GroupStatistics::Get(company, id_g, type).profit_last_year;
 
	return sum + GroupStatistics::Get(company, id_g, type).profit_last_year_min_age;
 
}
 

	
 
void RemoveAllGroupsForCompany(const CompanyID company)
 
{
 
	for (Group *g : Group::Iterate()) {
 
		if (company == g->owner) delete g;
 
	}
 
}
 

	
 

	
 
/**
 
 * Test if GroupID group is a descendant of (or is) GroupID search
src/group_gui.cpp
Show inline comments
 
@@ -293,31 +293,31 @@ private:
 

	
 
		/* draw autoreplace protection */
 
		x = rtl ? x - WidgetDimensions::scaled.hsep_wide - this->column_size[VGC_PROTECT].width : x + WidgetDimensions::scaled.hsep_wide + this->column_size[VGC_NAME].width;
 
		if (protection) DrawSprite(SPR_GROUP_REPLACE_PROTECT, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROTECT].height) / 2);
 

	
 
		/* draw autoreplace status */
 
		x = rtl ? x - WidgetDimensions::scaled.hsep_normal - this->column_size[VGC_AUTOREPLACE].width : x + WidgetDimensions::scaled.hsep_normal + this->column_size[VGC_PROTECT].width;
 
		if (stats.autoreplace_defined) DrawSprite(SPR_GROUP_REPLACE_ACTIVE, stats.autoreplace_finished ? PALETTE_CRASH : PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_AUTOREPLACE].height) / 2);
 

	
 
		/* draw the profit icon */
 
		x = rtl ? x - WidgetDimensions::scaled.hsep_normal - this->column_size[VGC_PROFIT].width : x + WidgetDimensions::scaled.hsep_normal + this->column_size[VGC_AUTOREPLACE].width;
 
		SpriteID spr;
 
		uint num_profit_vehicle = GetGroupNumProfitVehicle(this->vli.company, g_id, this->vli.vtype);
 
		Money profit_last_year = GetGroupProfitLastYear(this->vli.company, g_id, this->vli.vtype);
 
		if (num_profit_vehicle == 0) {
 
		uint num_vehicle_min_age = GetGroupNumVehicleMinAge(this->vli.company, g_id, this->vli.vtype);
 
		Money profit_last_year_min_age = GetGroupProfitLastYearMinAge(this->vli.company, g_id, this->vli.vtype);
 
		if (num_vehicle_min_age == 0) {
 
			spr = SPR_PROFIT_NA;
 
		} else if (profit_last_year < 0) {
 
		} else if (profit_last_year_min_age < 0) {
 
			spr = SPR_PROFIT_NEGATIVE;
 
		} else if (profit_last_year < VEHICLE_PROFIT_THRESHOLD * num_profit_vehicle) {
 
		} else if (profit_last_year_min_age < VEHICLE_PROFIT_THRESHOLD * num_vehicle_min_age) {
 
			spr = SPR_PROFIT_SOME;
 
		} else {
 
			spr = SPR_PROFIT_LOT;
 
		}
 
		DrawSprite(spr, PAL_NONE, x, y + (this->tiny_step_height - this->column_size[VGC_PROFIT].height) / 2);
 

	
 
		/* draw the number of vehicles of the group */
 
		x = rtl ? x - WidgetDimensions::scaled.hsep_normal - this->column_size[VGC_NUMBER].width : x + WidgetDimensions::scaled.hsep_normal + this->column_size[VGC_PROFIT].width;
 
		int num_vehicle_with_subgroups = GetGroupNumVehicle(this->vli.company, g_id, this->vli.vtype);
 
		int num_vehicle = GroupStatistics::Get(this->vli.company, g_id, this->vli.vtype).num_vehicle;
 
		if (IsAllGroupID(g_id) || IsDefaultGroupID(g_id) || num_vehicle_with_subgroups == num_vehicle) {
 
			SetDParam(0, num_vehicle);
src/vehicle.cpp
Show inline comments
 
@@ -1370,25 +1370,25 @@ bool Vehicle::HandleBreakdown()
 
			return false;
 
	}
 
}
 

	
 
/**
 
 * Update age of a vehicle.
 
 * @param v Vehicle to update.
 
 */
 
void AgeVehicle(Vehicle *v)
 
{
 
	if (v->age < MAX_DAY) {
 
		v->age++;
 
		if (v->IsPrimaryVehicle() && v->age == VEHICLE_PROFIT_MIN_AGE + 1) GroupStatistics::VehicleReachedProfitAge(v);
 
		if (v->IsPrimaryVehicle() && v->age == VEHICLE_PROFIT_MIN_AGE + 1) GroupStatistics::VehicleReachedMinAge(v);
 
	}
 

	
 
	if (!v->IsPrimaryVehicle() && (v->type != VEH_TRAIN || !Train::From(v)->IsEngine())) return;
 

	
 
	int age = v->age - v->max_age;
 
	if (age == DAYS_IN_LEAP_YEAR * 0 || age == DAYS_IN_LEAP_YEAR * 1 ||
 
			age == DAYS_IN_LEAP_YEAR * 2 || age == DAYS_IN_LEAP_YEAR * 3 || age == DAYS_IN_LEAP_YEAR * 4) {
 
		v->reliability_spd_dec <<= 1;
 
	}
 

	
 
	SetWindowDirty(WC_VEHICLE_DETAILS, v->index);
 

	
0 comments (0 inline, 0 general)