|
|
/* $Id$ */
|
|
|
|
|
|
/** @file industry_cmd.cpp */
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
#include "openttd.h"
|
|
|
#include "clear_map.h"
|
|
|
#include "functions.h"
|
|
|
#include "industry_map.h"
|
|
|
#include "station_map.h"
|
|
|
#include "table/strings.h"
|
|
|
#include "strings.h"
|
|
|
#include "table/sprites.h"
|
|
|
#include "map.h"
|
|
|
#include "tile.h"
|
|
|
#include "train.h"
|
|
|
#include "landscape.h"
|
|
|
#include "viewport.h"
|
|
|
#include "command.h"
|
|
|
#include "industry.h"
|
|
|
#include "town.h"
|
|
|
#include "vehicle.h"
|
|
|
#include "news.h"
|
|
|
#include "saveload.h"
|
|
|
#include "economy.h"
|
|
|
#include "sound.h"
|
|
|
#include "variables.h"
|
|
|
#include "table/industry_land.h"
|
|
|
#include "table/build_industry.h"
|
|
|
#include "genworld.h"
|
|
|
#include "date.h"
|
|
|
#include "water_map.h"
|
|
|
#include "tree_map.h"
|
|
|
#include "cargotype.h"
|
|
|
#include "newgrf.h"
|
|
|
#include "newgrf_commons.h"
|
|
|
#include "newgrf_industries.h"
|
|
|
#include "newgrf_industrytiles.h"
|
|
|
#include "newgrf_callbacks.h"
|
|
|
#include "misc/autoptr.hpp"
|
|
@@ -1835,48 +1836,176 @@ static void MaybeNewIndustry(void)
|
|
|
} else {
|
|
|
SetDParam(1, ind->town->index);
|
|
|
}
|
|
|
AddNewsItem(ind_spc->new_industry_text,
|
|
|
NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_TILE, NT_OPENCLOSE, 0), ind->xy, 0);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Protects an industry from closure if the appropriate flags and conditions are met
|
|
|
* INDUSTRYBEH_CANCLOSE_LASTINSTANCE must be set (which, by default, it is not) and the
|
|
|
* count of industries of this type must one (or lower) in order to be protected
|
|
|
* against closure.
|
|
|
* @param type IndustryType been queried
|
|
|
* @result true if protection is on, false otherwise (except for oil wells)
|
|
|
*/
|
|
|
static bool CheckIndustryCloseDownProtection(IndustryType type)
|
|
|
{
|
|
|
const IndustrySpec *indspec = GetIndustrySpec(type);
|
|
|
|
|
|
/* oil wells (or the industries with that flag set) are always allowed to closedown */
|
|
|
if (indspec->behaviour & INDUSTRYBEH_DONT_INCR_PROD && _opt.landscape == LT_TEMPERATE) return false;
|
|
|
return (indspec->behaviour & INDUSTRYBEH_CANCLOSE_LASTINSTANCE) == 0 && GetIndustryTypeCount(type) <= 1;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Can given cargo type be accepted or produced by the industry?
|
|
|
* @param cargo: Cargo type
|
|
|
* @param ind: Industry
|
|
|
* @param *c_accepts: Pointer to boolean for acceptance of cargo
|
|
|
* @param *c_produces: Pointer to boolean for production of cargo
|
|
|
* @return: \c *c_accepts is set when industry accepts the cargo type,
|
|
|
* \c *c_produces is set when the industry produces the cargo type
|
|
|
*/
|
|
|
static void CanCargoServiceIndustry(CargoID cargo, Industry *ind, bool *c_accepts, bool *c_produces)
|
|
|
{
|
|
|
const IndustrySpec *indspec = GetIndustrySpec(ind->type);
|
|
|
|
|
|
/* Check for acceptance of cargo */
|
|
|
for (uint j = 0; j < lengthof(ind->accepts_cargo) && ind->accepts_cargo[j] != CT_INVALID; j++) {
|
|
|
if (cargo == ind->accepts_cargo[j]) {
|
|
|
if (HASBIT(indspec->callback_flags, CBM_IND_REFUSE_CARGO)) {
|
|
|
uint16 res = GetIndustryCallback(CBID_INDUSTRY_REFUSE_CARGO,
|
|
|
0, GetReverseCargoTranslation(cargo, indspec->grf_prop.grffile),
|
|
|
ind, ind->type, ind->xy);
|
|
|
if (res == 0) continue;
|
|
|
}
|
|
|
*c_accepts = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Check for produced cargo */
|
|
|
for (uint j = 0; j < lengthof(ind->produced_cargo) && ind->produced_cargo[j] != CT_INVALID; j++) {
|
|
|
if (cargo == ind->produced_cargo[j]) {
|
|
|
*c_produces = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Compute who can service the industry.
|
|
|
*
|
|
|
* Here, 'can service' means that he/she has trains and stations close enough
|
|
|
* to the industry with the right cargo type and the right orders (ie has the
|
|
|
* technical means).
|
|
|
*
|
|
|
* @param ind: Industry being investigated.
|
|
|
*
|
|
|
* @return: 0 if nobody can service the industry, 2 if the local player can
|
|
|
* service the industry, and 1 otherwise (only competitors can service the
|
|
|
* industry)
|
|
|
*/
|
|
|
int WhoCanServiceIndustry(Industry* ind)
|
|
|
{
|
|
|
/* Find all stations within reach of the industry */
|
|
|
StationSet stations = FindStationsAroundIndustryTile(ind->xy, ind->width, ind->height);
|
|
|
|
|
|
if (stations.size() == 0) return 0; // No stations found at all => nobody services
|
|
|
|
|
|
const Vehicle *v;
|
|
|
int result = 0;
|
|
|
FOR_ALL_VEHICLES(v) {
|
|
|
/* Is it worthwhile to try this vehicle? */
|
|
|
if (v->owner != _local_player && result != 0) continue;
|
|
|
|
|
|
/* Check whether it accepts the right kind of cargo */
|
|
|
bool c_accepts = false;
|
|
|
bool c_produces = false;
|
|
|
if (v->type == VEH_TRAIN && IsFrontEngine(v)) {
|
|
|
const Vehicle *u = v;
|
|
|
BEGIN_ENUM_WAGONS(u)
|
|
|
CanCargoServiceIndustry(u->cargo_type, ind, &c_accepts, &c_produces);
|
|
|
END_ENUM_WAGONS(u)
|
|
|
} else if (v->type == VEH_ROAD || v->type == VEH_SHIP || v->type == VEH_AIRCRAFT) {
|
|
|
CanCargoServiceIndustry(v->cargo_type, ind, &c_accepts, &c_produces);
|
|
|
} else {
|
|
|
continue;
|
|
|
}
|
|
|
if (!c_accepts && !c_produces) continue; // Wrong cargo
|
|
|
|
|
|
/* Check orders of the vehicle.
|
|
|
* We cannot check the first of shared orders only, since the first vehicle in such a chain
|
|
|
* may have a different cargo type.
|
|
|
*/
|
|
|
const Order *o;
|
|
|
FOR_VEHICLE_ORDERS(v, o) {
|
|
|
if (o->type == OT_GOTO_STATION && !HASBIT(o->flags, OFB_TRANSFER)) {
|
|
|
/* Vehicle visits a station to load or unload */
|
|
|
Station *st = GetStation(o->dest);
|
|
|
if (!st->IsValid()) continue;
|
|
|
|
|
|
/* Same cargo produced by industry is dropped here => not serviced by vehicle v */
|
|
|
if (HASBIT(o->flags, OFB_UNLOAD) && !c_accepts) break;
|
|
|
|
|
|
if (stations.find(st) != stations.end()) {
|
|
|
if (v->owner == _local_player) return 2; // Player services industry
|
|
|
result = 1; // Competitor services industry
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Report news that industry production has changed significantly
|
|
|
*
|
|
|
* @param ind: Industry with changed production
|
|
|
* @param type: Cargo type that has changed
|
|
|
* @param percent: Percentage of change (>0 means increase, <0 means decrease)
|
|
|
*/
|
|
|
static void ReportNewsProductionChangeIndustry(Industry *ind, CargoID type, int percent)
|
|
|
{
|
|
|
NewsType nt;
|
|
|
|
|
|
switch (WhoCanServiceIndustry(ind)) {
|
|
|
case 0: nt = NT_INDUSTRY_NOBODY; break;
|
|
|
case 1: nt = NT_INDUSTRY_OTHER; break;
|
|
|
case 2: nt = NT_INDUSTRY_PLAYER; break;
|
|
|
default: NOT_REACHED(); break;
|
|
|
}
|
|
|
SetDParam(2, abs(percent));
|
|
|
SetDParam(0, GetCargo(type)->name);
|
|
|
SetDParam(1, ind->index);
|
|
|
AddNewsItem(
|
|
|
percent >= 0 ? STR_INDUSTRY_PROD_GOUP : STR_INDUSTRY_PROD_GODOWN,
|
|
|
NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_TILE, nt, 0),
|
|
|
ind->xy + TileDiffXY(1, 1), 0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/** Change industry production or do closure
|
|
|
* @param i Industry for which changes are performed
|
|
|
* @param monthly true if it's the monthly call, false if it's the random call
|
|
|
*/
|
|
|
static void ChangeIndustryProduction(Industry *i, bool monthly)
|
|
|
{
|
|
|
extern StringID MapGRFStringID(uint32 grfid, StringID str);
|
|
|
StringID str = STR_NULL;
|
|
|
bool closeit = false;
|
|
|
const IndustrySpec *indspec = GetIndustrySpec(i->type);
|
|
|
bool standard = true;
|
|
|
bool suppress_message = false;
|
|
|
/* don't use smooth economy for industries using production callbacks */
|
|
|
bool smooth_economy = _patches.smooth_economy && !(HASBIT(indspec->callback_flags, CBM_IND_PRODUCTION_256_TICKS) || HASBIT(indspec->callback_flags, CBM_IND_PRODUCTION_CARGO_ARRIVAL));
|
|
|
byte div = 0;
|
|
|
byte mul = 0;
|
|
|
|
|
|
if (HASBIT(indspec->callback_flags, monthly ? CBM_IND_MONTHLYPROD_CHANGE : CBM_IND_PRODUCTION_CHANGE)) {
|
|
|
uint16 res = GetIndustryCallback(monthly ? CBID_INDUSTRY_MONTHLYPROD_CHANGE : CBID_INDUSTRY_PRODUCTION_CHANGE, 0, Random(), i, i->type, i->xy);
|
|
|
if (res != CALLBACK_FAILED) {
|
|
|
standard = false;
|
|
|
suppress_message = HASBIT(res, 7);
|
|
|
/* Get the custom message if any */
|
|
|
if (HASBIT(res, 8)) str = MapGRFStringID(indspec->grf_prop.grffile->grfid, GB(GetRegister(0x100), 0, 16));
|
|
@@ -1888,141 +2017,146 @@ static void ChangeIndustryProduction(Ind
|
|
|
case 0x2: mul = 1; break; // Double industry production if it hasn't reached eight times of the original yet.
|
|
|
case 0x3: closeit = true; break; // The industry announces imminent closure, and is physically removed from the map next month.
|
|
|
case 0x4: standard = true; break; // Do the standard random production change as if this industry was a primary one.
|
|
|
case 0x5: case 0x6: case 0x7: // Divide production by 4, 8, 16
|
|
|
case 0x8: div = res - 0x5; break; // Divide production by 32
|
|
|
case 0x9: case 0xA: case 0xB: // Multiply production by 4, 8, 16
|
|
|
case 0xC: mul = res - 0x9; break; // Multiply production by 32
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (standard && monthly != smooth_economy) return;
|
|
|
|
|
|
if (standard && indspec->life_type == INDUSTRYLIFE_BLACK_HOLE) return;
|
|
|
|
|
|
if (standard && (indspec->life_type & (INDUSTRYLIFE_ORGANIC | INDUSTRYLIFE_EXTRACTIVE)) != 0) {
|
|
|
/* decrease or increase */
|
|
|
bool only_decrease = (indspec->behaviour & INDUSTRYBEH_DONT_INCR_PROD) && _opt.landscape == LT_TEMPERATE;
|
|
|
|
|
|
if (smooth_economy) {
|
|
|
closeit = true;
|
|
|
for (byte j = 0; j < 2 && i->produced_cargo[j] != CT_INVALID; j++){
|
|
|
uint32 r = Random();
|
|
|
int old_prod, new_prod, percent;
|
|
|
int mag;
|
|
|
|
|
|
new_prod = old_prod = i->production_rate[j];
|
|
|
|
|
|
if (CHANCE16I(20, 1024, r)) new_prod -= max(((RandomRange(50) + 10) * old_prod) >> 8, 1U);
|
|
|
/* Chance of increasing becomes better when more is transported */
|
|
|
if (CHANCE16I(20 + (i->last_month_pct_transported[j] * 20 >> 8), 1024, r >> 16) && !only_decrease) {
|
|
|
new_prod += max(((RandomRange(50) + 10) * old_prod) >> 8, 1U);
|
|
|
}
|
|
|
|
|
|
new_prod = clamp(new_prod, 1, 255);
|
|
|
/* Do not stop closing the industry when it has the lowest possible production rate */
|
|
|
if (new_prod == old_prod && old_prod > 1) {
|
|
|
closeit = false;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
percent = (old_prod == 0) ? 100 : (new_prod * 100 / old_prod - 100);
|
|
|
i->production_rate[j] = new_prod;
|
|
|
|
|
|
/* Close the industry when it has the lowest possible production rate */
|
|
|
if (new_prod > 1) closeit = false;
|
|
|
|
|
|
mag = abs(percent);
|
|
|
if (mag >= 10) {
|
|
|
SetDParam(2, mag);
|
|
|
SetDParam(0, GetCargo(i->produced_cargo[j])->name);
|
|
|
SetDParam(1, i->index);
|
|
|
AddNewsItem(
|
|
|
percent >= 0 ? STR_INDUSTRY_PROD_GOUP : STR_INDUSTRY_PROD_GODOWN,
|
|
|
NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_TILE, NT_ECONOMY, 0),
|
|
|
i->xy + TileDiffXY(1, 1), 0
|
|
|
);
|
|
|
if (abs(percent) >= 10) {
|
|
|
ReportNewsProductionChangeIndustry(i, i->produced_cargo[j], percent);
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
if (only_decrease || CHANCE16(1, 3)) {
|
|
|
/* If you transport > 60%, 66% chance we increase, else 33% chance we increase */
|
|
|
if (!only_decrease && (i->last_month_pct_transported[0] > 153) != CHANCE16(1, 3)) {
|
|
|
mul = 1; // Increase production
|
|
|
} else {
|
|
|
div = 1; // Decrease production
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (standard && indspec->life_type & INDUSTRYLIFE_PROCESSING) {
|
|
|
if ( (byte)(_cur_year - i->last_prod_year) >= 5 && CHANCE16(1, smooth_economy ? 180 : 2)) {
|
|
|
closeit = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Increase if needed */
|
|
|
while (mul-- != 0 && i->prod_level < 0x80) {
|
|
|
i->prod_level <<= 1;
|
|
|
i->production_rate[0] = min(i->production_rate[0] * 2, 0xFF);
|
|
|
i->production_rate[1] = min(i->production_rate[1] * 2, 0xFF);
|
|
|
if (str == STR_NULL) str = indspec->production_up_text;
|
|
|
}
|
|
|
|
|
|
/* Decrease if needed */
|
|
|
while (div-- != 0 && !closeit) {
|
|
|
if (i->prod_level == 4) {
|
|
|
closeit = true;
|
|
|
} else {
|
|
|
i->prod_level >>= 1;
|
|
|
i->production_rate[0] = (i->production_rate[0] + 1) >> 1;
|
|
|
i->production_rate[1] = (i->production_rate[1] + 1) >> 1;
|
|
|
if (str == STR_NULL) str = indspec->production_down_text;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Close if needed and allowed */
|
|
|
if (closeit && !CheckIndustryCloseDownProtection(i->type)) {
|
|
|
i->prod_level = 0;
|
|
|
str = indspec->closure_text;
|
|
|
}
|
|
|
|
|
|
if (!suppress_message && str != STR_NULL) {
|
|
|
NewsType nt;
|
|
|
/* Compute news category */
|
|
|
if (closeit) {
|
|
|
nt = NT_OPENCLOSE;
|
|
|
} else {
|
|
|
switch (WhoCanServiceIndustry(i)) {
|
|
|
case 0: nt = NT_INDUSTRY_NOBODY; break;
|
|
|
case 1: nt = NT_INDUSTRY_OTHER; break;
|
|
|
case 2: nt = NT_INDUSTRY_PLAYER; break;
|
|
|
default: NOT_REACHED(); break;
|
|
|
}
|
|
|
}
|
|
|
/* Set parameters of news string */
|
|
|
if (str > STR_LAST_STRINGID) {
|
|
|
SetDParam(0, STR_TOWN);
|
|
|
SetDParam(1, i->town->index);
|
|
|
SetDParam(2, indspec->name);
|
|
|
} else if (closeit) {
|
|
|
SetDParam(0, STR_INDUSTRY_FORMAT);
|
|
|
SetDParam(1, i->town->index);
|
|
|
SetDParam(2, indspec->name);
|
|
|
} else {
|
|
|
SetDParam(0, i->index);
|
|
|
}
|
|
|
/* and report the news to the user */
|
|
|
AddNewsItem(str,
|
|
|
NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_TILE, closeit ? NT_OPENCLOSE : NT_ECONOMY, 0),
|
|
|
NEWS_FLAGS(NM_THIN, NF_VIEWPORT | NF_TILE, nt, 0),
|
|
|
i->xy + TileDiffXY(1, 1), 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void IndustryMonthlyLoop()
|
|
|
{
|
|
|
Industry *i;
|
|
|
PlayerID old_player = _current_player;
|
|
|
_current_player = OWNER_NONE;
|
|
|
|
|
|
FOR_ALL_INDUSTRIES(i) {
|
|
|
UpdateIndustryStatistics(i);
|
|
|
if (i->prod_level == 0) {
|
|
|
delete i;
|
|
|
} else {
|
|
|
ChangeIndustryProduction(i, true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 3% chance that we start a new industry */
|
|
|
if (CHANCE16(3, 100)) {
|
|
|
MaybeNewIndustry();
|
|
|
} else {
|
|
|
i = GetRandomIndustry();
|
|
@@ -2063,49 +2197,49 @@ Money IndustrySpec::GetConstructionCost(
|
|
|
)) >> 8;
|
|
|
}
|
|
|
|
|
|
static CommandCost TerraformTile_Industry(TileIndex tile, uint32 flags, uint z_new, Slope tileh_new)
|
|
|
{
|
|
|
if (AutoslopeEnabled()) {
|
|
|
/* We imitate here TTDP's behaviour:
|
|
|
* - Both new and old slope must not be steep.
|
|
|
* - TileMaxZ must not be changed.
|
|
|
* - Allow autoslope by default.
|
|
|
* - Disallow autoslope if callback succeeds and returns non-zero.
|
|
|
*/
|
|
|
Slope tileh_old = GetTileSlope(tile, NULL);
|
|
|
/* TileMaxZ must not be changed. Slopes must not be steep. */
|
|
|
if (!IsSteepSlope(tileh_old) && !IsSteepSlope(tileh_new) && (GetTileMaxZ(tile) == z_new + GetSlopeMaxZ(tileh_new))) {
|
|
|
const IndustryGfx gfx = GetIndustryGfx(tile);
|
|
|
const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx);
|
|
|
|
|
|
/* Call callback 3C 'disable autosloping for industry tiles'. */
|
|
|
if (HASBIT(itspec->callback_flags, CBM_INDT_AUTOSLOPE)) {
|
|
|
/* If the callback fails, allow autoslope. */
|
|
|
uint16 res = GetIndustryTileCallback(CBID_INDUSTRY_AUTOSLOPE, 0, 0, gfx, GetIndustryByTile(tile), tile);
|
|
|
if ((res == 0) || (res == CALLBACK_FAILED)) return _price.terraform;
|
|
|
} else {
|
|
|
// allow autoslope
|
|
|
/* allow autoslope */
|
|
|
return _price.terraform;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR);
|
|
|
}
|
|
|
|
|
|
extern const TileTypeProcs _tile_type_industry_procs = {
|
|
|
DrawTile_Industry, /* draw_tile_proc */
|
|
|
GetSlopeZ_Industry, /* get_slope_z_proc */
|
|
|
ClearTile_Industry, /* clear_tile_proc */
|
|
|
GetAcceptedCargo_Industry, /* get_accepted_cargo_proc */
|
|
|
GetTileDesc_Industry, /* get_tile_desc_proc */
|
|
|
GetTileTrackStatus_Industry, /* get_tile_track_status_proc */
|
|
|
ClickTile_Industry, /* click_tile_proc */
|
|
|
AnimateTile_Industry, /* animate_tile_proc */
|
|
|
TileLoop_Industry, /* tile_loop_proc */
|
|
|
ChangeTileOwner_Industry, /* change_tile_owner_proc */
|
|
|
GetProducedCargo_Industry, /* get_produced_cargo_proc */
|
|
|
NULL, /* vehicle_enter_tile_proc */
|
|
|
GetFoundation_Industry, /* get_foundation_proc */
|
|
|
TerraformTile_Industry, /* terraform_tile_proc */
|
|
|
};
|
|
|
|