diff --git a/src/command.cpp b/src/command.cpp index 453f305d38..9fbc6cae6a 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -97,6 +97,7 @@ CommandProc CmdInsertOrder; CommandProc CmdChangeServiceInt; CommandProc CmdBuildIndustry; +CommandProc CmdIndustryCtrl; CommandProc CmdSetCompanyManagerFace; CommandProc CmdSetCompanyColour; @@ -265,6 +266,8 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdChangeServiceInt, 0, CMDT_VEHICLE_MANAGEMENT ), // CMD_CHANGE_SERVICE_INT DEF_CMD(CmdBuildIndustry, CMD_DEITY, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_INDUSTRY + DEF_CMD(CmdIndustryCtrl, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_INDUSTRY_CTRL + DEF_CMD(CmdSetCompanyManagerFace, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_MANAGER_FACE DEF_CMD(CmdSetCompanyColour, 0, CMDT_OTHER_MANAGEMENT ), // CMD_SET_COMPANY_COLOUR diff --git a/src/command_type.h b/src/command_type.h index 04dbfe893c..0620b96b63 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -230,6 +230,7 @@ enum Commands { CMD_CHANGE_SERVICE_INT, ///< change the server interval of a vehicle CMD_BUILD_INDUSTRY, ///< build a new industry + CMD_INDUSTRY_CTRL, ///< change industry properties CMD_SET_COMPANY_MANAGER_FACE, ///< set the manager's face of the company CMD_SET_COMPANY_COLOUR, ///< set the colour of the company diff --git a/src/industry.h b/src/industry.h index e82033dd13..feee6f2008 100644 --- a/src/industry.h +++ b/src/industry.h @@ -34,6 +34,26 @@ enum ProductionLevels { PRODLEVEL_MAXIMUM = 0x80, ///< the industry is running at full speed }; +/** + * Flags to control/override the behaviour of an industry. + * These flags are controlled by game scripts. + */ +enum IndustryControlFlags : byte { + /** No flags in effect */ + INDCTL_NONE = 0, + /** When industry production change is evaluated, rolls to decrease are ignored. */ + INDCTL_NO_PRODUCTION_DECREASE = 1 << 0, + /** When industry production change is evaluated, rolls to increase are ignored. */ + INDCTL_NO_PRODUCTION_INCREASE = 1 << 1, + /** + * Industry can not close regardless of production level or time since last delivery. + * This does not prevent a closure already announced. */ + INDCTL_NO_CLOSURE = 1 << 2, + /** Mask of all flags set */ + INDCTL_MASK = INDCTL_NO_PRODUCTION_DECREASE | INDCTL_NO_PRODUCTION_INCREASE | INDCTL_NO_CLOSURE, +}; +DECLARE_ENUM_AS_BIT_SET(IndustryControlFlags); + /** * Defines the internal data of a functional industry. */ @@ -59,6 +79,7 @@ struct Industry : IndustryPool::PoolItem<&_industry_pool> { byte random_colour; ///< randomized colour of the industry, for display purpose Year last_prod_year; ///< last year of production byte was_cargo_delivered; ///< flag that indicate this has been the closest industry chosen for cargo delivery by a station. see DeliverGoodsToIndustry + IndustryControlFlags ctlflags; ///< flags overriding standard behaviours PartOfSubsidy part_of_subsidy; ///< NOSAVE: is this industry a source/destination of a subsidy? StationList stations_near; ///< NOSAVE: List of nearby stations. diff --git a/src/industry_cmd.cpp b/src/industry_cmd.cpp index b8b92e5bf9..420244e6dc 100644 --- a/src/industry_cmd.cpp +++ b/src/industry_cmd.cpp @@ -1753,6 +1753,7 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type, i->was_cargo_delivered = false; i->last_prod_year = _cur_year; i->founder = founder; + i->ctlflags = INDCTL_NONE; i->construction_date = _date; i->construction_type = (_game_mode == GM_EDITOR) ? ICT_SCENARIO_EDITOR : @@ -2049,6 +2050,43 @@ CommandCost CmdBuildIndustry(TileIndex tile, DoCommandFlag flags, uint32 p1, uin return CommandCost(EXPENSES_OTHER, indspec->GetConstructionCost()); } +/** + * Change industry properties + * @param tile Unused. + * @param flags Type of operation. + * @param p1 IndustryID + * @param p2 various bitstuffed elements + * - p2 = (bit 0 - 7) - action to perform: + * 0 = set control flags + * - p2 = (bit 8 - 15) - IndustryControlFlags + * (only used with set control flags) + * @param text unused + * @return Empty cost or an error. + */ +CommandCost CmdIndustryCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (_current_company != OWNER_DEITY) return CMD_ERROR; + + Industry *ind = Industry::GetIfValid(p1); + if (ind == nullptr) return CMD_ERROR; + + uint8 action = GB(p2, 0, 8); + + switch (action) { + case 0: { + IndustryControlFlags ctlflags = (IndustryControlFlags)GB(p2, 8, 8) & INDCTL_MASK; + + if (flags & DC_EXEC) ind->ctlflags = ctlflags; + + break; + } + + default: + NOT_REACHED(); + } + + return CommandCost(); +} /** * Create a new industry of random layout. @@ -2659,7 +2697,7 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) } } } else if (_settings_game.economy.type == ET_SMOOTH) { - closeit = true; + closeit = !(i->ctlflags & (INDCTL_NO_CLOSURE | INDCTL_NO_PRODUCTION_DECREASE)); for (byte j = 0; j < lengthof(i->produced_cargo); j++) { if (i->produced_cargo[j] == CT_INVALID) continue; uint32 r = Random(); @@ -2692,6 +2730,10 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) new_prod = Clamp(new_prod, 0, 16); } + /* If override flags are set, prevent actually changing production if any was decided on */ + if ((i->ctlflags & INDCTL_NO_PRODUCTION_DECREASE) && new_prod < old_prod) continue; + if ((i->ctlflags & INDCTL_NO_PRODUCTION_INCREASE) && new_prod > old_prod) continue; + /* Do not stop closing the industry when it has the lowest possible production rate */ if (new_prod == old_prod && old_prod > 1) { closeit = false; @@ -2711,6 +2753,10 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) } } + /* If override flags are set, prevent actually changing production if any was decided on */ + if ((i->ctlflags & INDCTL_NO_PRODUCTION_DECREASE) && (div > 0 || increment < 0)) return; + if ((i->ctlflags & INDCTL_NO_PRODUCTION_INCREASE) && (mul > 0 || increment > 0)) return; + if (!callback_enabled && (indspec->life_type & INDUSTRYLIFE_PROCESSING)) { if ( (byte)(_cur_year - i->last_prod_year) >= 5 && Chance16(1, original_economy ? 2 : 180)) { closeit = true; @@ -2728,6 +2774,7 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) while (div-- != 0 && !closeit) { if (i->prod_level == PRODLEVEL_MINIMUM) { closeit = true; + break; } else { i->prod_level = max(i->prod_level / 2, (int)PRODLEVEL_MINIMUM); // typecast to int required to please MSVC recalculate_multipliers = true; @@ -2750,7 +2797,7 @@ static void ChangeIndustryProduction(Industry *i, bool monthly) if (recalculate_multipliers) i->RecomputeProductionMultipliers(); /* Close if needed and allowed */ - if (closeit && !CheckIndustryCloseDownProtection(i->type)) { + if (closeit && !CheckIndustryCloseDownProtection(i->type) && !(i->ctlflags & INDCTL_NO_CLOSURE)) { i->prod_level = PRODLEVEL_CLOSURE; SetWindowDirty(WC_INDUSTRY_VIEW, i->index); str = indspec->closure_text; diff --git a/src/newgrf_industries.cpp b/src/newgrf_industries.cpp index a8748a4953..2dbe907dee 100644 --- a/src/newgrf_industries.cpp +++ b/src/newgrf_industries.cpp @@ -248,6 +248,9 @@ static uint32 GetCountAndDistanceOfClosestInstance(byte param_setID, byte layout case 0x46: return this->industry->construction_date; // Date when built - long format - (in days) + /* Override flags from GS */ + case 0x47: return this->industry->ctlflags; + /* Get industry ID at offset param */ case 0x60: return GetIndustryIDAtOffset(GetNearbyTile(parameter, this->industry->location.tile, false), this->industry, this->ro.grffile->grfid); diff --git a/src/saveload/industry_sl.cpp b/src/saveload/industry_sl.cpp index f5b1464c1e..8f7301120b 100644 --- a/src/saveload/industry_sl.cpp +++ b/src/saveload/industry_sl.cpp @@ -56,6 +56,7 @@ static const SaveLoad _industry_desc[] = { SLE_CONDVAR(Industry, last_prod_year, SLE_FILE_U8 | SLE_VAR_I32, SL_MIN_VERSION, SLV_31), SLE_CONDVAR(Industry, last_prod_year, SLE_INT32, SLV_31, SL_MAX_VERSION), SLE_VAR(Industry, was_cargo_delivered, SLE_UINT8), + SLE_CONDVAR(Industry, ctlflags, SLE_UINT8, SLV_GS_INDUSTRY_CONTROL, SL_MAX_VERSION), SLE_CONDVAR(Industry, founder, SLE_UINT8, SLV_70, SL_MAX_VERSION), SLE_CONDVAR(Industry, construction_date, SLE_INT32, SLV_70, SL_MAX_VERSION), diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h index 7fa91a5095..0b525570d7 100644 --- a/src/saveload/saveload.h +++ b/src/saveload/saveload.h @@ -320,6 +320,8 @@ enum SaveLoadVersion : uint16 { SLV_START_PATCHPACKS, ///< 220 First known patchpack to use a version just above ours. SLV_END_PATCHPACKS = 286, ///< 286 Last known patchpack to use a version just above ours. + SLV_GS_INDUSTRY_CONTROL, ///< 287 PR#7912 GS industry control. + SL_MAX_VERSION, ///< Highest possible saveload version }; diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp index fc917a0120..abc9e9c35e 100644 --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -21,6 +21,10 @@ * \li GSEventStoryPageButtonClick * \li GSEventStoryPageTileSelect * \li GSEventStoryPageVehicleSelect + * \li GSIndustry::GetCargoLastAcceptedDate + * \li GSIndustry::GetControlFlags + * \li GSIndustry::GetLastProductionYear + * \li GSIndustry::SetControlFlags * \li GSStoryPage::MakePushButtonReference * \li GSStoryPage::MakeTileButtonReference * \li GSStoryPage::MakeVehicleButtonReference diff --git a/src/script/api/script_cargo.hpp b/src/script/api/script_cargo.hpp index 4aab97c70e..9ecd4cbfb2 100644 --- a/src/script/api/script_cargo.hpp +++ b/src/script/api/script_cargo.hpp @@ -57,6 +57,7 @@ public: /* Note: these values represent part of the in-game CargoTypes enum */ CT_AUTO_REFIT = ::CT_AUTO_REFIT, ///< Automatically choose cargo type when doing auto-refitting. CT_NO_REFIT = ::CT_NO_REFIT, ///< Do not refit cargo of a vehicle. + CT_INVALID = ::CT_INVALID, ///< An invalid cargo type. }; /** diff --git a/src/script/api/script_industry.cpp b/src/script/api/script_industry.cpp index 4d1e91a3ce..c2b449b0ba 100644 --- a/src/script/api/script_industry.cpp +++ b/src/script/api/script_industry.cpp @@ -16,6 +16,7 @@ #include "../../station_base.h" #include "../../newgrf_industries.h" #include "table/strings.h" +#include #include "../../safeguards.h" @@ -204,3 +205,39 @@ return ::Industry::Get(industry_id)->type; } + +int32 ScriptIndustry::GetLastProductionYear(IndustryID industry_id) +{ + Industry *i = Industry::GetIfValid(industry_id); + if (i == nullptr) return 0; + return i->last_prod_year; +} + +ScriptDate::Date ScriptIndustry::GetCargoLastAcceptedDate(IndustryID industry_id, CargoID cargo_type) +{ + Industry *i = Industry::GetIfValid(industry_id); + if (i == nullptr) return ScriptDate::DATE_INVALID; + + if (cargo_type == CT_INVALID) { + return (ScriptDate::Date)std::accumulate(std::begin(i->last_cargo_accepted_at), std::end(i->last_cargo_accepted_at), 0, [](Date a, Date b) { return std::max(a, b); }); + } else { + int index = i->GetCargoAcceptedIndex(cargo_type); + if (index < 0) return ScriptDate::DATE_INVALID; + return (ScriptDate::Date)i->last_cargo_accepted_at[index]; + } +} + +uint32 ScriptIndustry::GetControlFlags(IndustryID industry_id) +{ + Industry *i = Industry::GetIfValid(industry_id); + if (i == nullptr) return 0; + return i->ctlflags; +} + +bool ScriptIndustry::SetControlFlags(IndustryID industry_id, uint32 control_flags) +{ + if (ScriptObject::GetCompany() != OWNER_DEITY) return false; + if (!IsValidIndustry(industry_id)) return false; + + return ScriptObject::DoCommand(0, industry_id, 0 | ((control_flags & ::INDCTL_MASK) << 8), CMD_INDUSTRY_CTRL); +} diff --git a/src/script/api/script_industry.hpp b/src/script/api/script_industry.hpp index 98c7d33ac3..2fbd861cc5 100644 --- a/src/script/api/script_industry.hpp +++ b/src/script/api/script_industry.hpp @@ -11,6 +11,8 @@ #define SCRIPT_INDUSTRY_HPP #include "script_object.hpp" +#include "script_date.hpp" +#include "../../industry.h" /** * Class that handles all industry related functions. @@ -25,6 +27,27 @@ public: CAS_TEMP_REFUSED, ///< The industry temporarily refuses to accept this CargoID but may do so again in the future. }; + /** + * Control flags for industry + * @api -ai + */ + enum IndustryControlFlags { + /** + * When industry production change is evaluated, rolls to decrease are ignored. + * This also prevents industry closure due to production dropping to the lowest level. + */ + INDCTL_NO_PRODUCTION_DECREASE = ::INDCTL_NO_PRODUCTION_DECREASE, + /** + * When industry production change is evaluated, rolls to increase are ignored. + */ + INDCTL_NO_PRODUCTION_INCREASE = ::INDCTL_NO_PRODUCTION_INCREASE, + /** + * Industry can not close regardless of production level or time since last delivery. + * This does not prevent a closure already announced. + */ + INDCTL_NO_CLOSURE = ::INDCTL_NO_CLOSURE, + }; + /** * Gets the number of industries. * @return The number of industries. @@ -196,6 +219,46 @@ public: * @return The IndustryType of the industry. */ static IndustryType GetIndustryType(IndustryID industry_id); + + /** + * Get the last year this industry had any production output. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return Year the industry last had production, 0 if error. + * @api -ai + */ + static int32 GetLastProductionYear(IndustryID industry_id); + + /** + * Get the last date this industry accepted any cargo delivery. + * @param industry_id The index of the industry. + * @param cargo_type The cargo to query, or CT_INVALID to query latest of all accepted cargoes. + * @pre IsValidIndustry(industry_id). + * @pre IsValidCargo(cargo_type) || cargo_type == CT_INVALID. + * @return Date the industry last received cargo from a delivery, or ScriptDate::DATE_INVALID on error. + * @api -ai + */ + static ScriptDate::Date GetCargoLastAcceptedDate(IndustryID industry_id, CargoID cargo_type); + + /** + * Get the current control flags for an industry. + * @param industry_id The index of the industry. + * @pre IsValidIndustry(industry_id). + * @return Bit flags of the IndustryControlFlags enumeration. + * @api -ai + */ + static uint32 GetControlFlags(IndustryID industry_id); + + /** + * Change the control flags for an industry. + * @param industry_id The index of the industry. + * @param control_flags New flags as a combination of IndustryControlFlags values. + * @pre IsValidIndustry(industry_id). + * @pre No ScriptCompanyMode may be in scope. + * @return True if the action succeeded. + * @api -ai + */ + static bool SetControlFlags(IndustryID industry_id, uint32 control_flags); }; #endif /* SCRIPT_INDUSTRY_HPP */