OpenTTD Source 20260512-master-g20b387b91f
ai_core.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
13#include "../company_base.h"
14#include "../company_func.h"
15#include "../network/network.h"
16#include "../window_func.h"
17#include "../framerate_type.h"
18#include "ai_scanner.hpp"
19#include "ai_instance.hpp"
20#include "ai_config.hpp"
21#include "ai_info.hpp"
22#include "ai.hpp"
23
24#include "../safeguards.h"
25
26/* static */ uint AI::frame_counter = 0;
27/* static */ std::unique_ptr<AIScannerInfo> AI::scanner_info = nullptr;
28/* static */ std::unique_ptr<AIScannerLibrary> AI::scanner_library = nullptr;
29
30/* static */ bool AI::CanStartNew()
31{
32 /* Only allow new AIs on the server and only when that is allowed in multiplayer */
33 return !_networking || (_network_server && _settings_game.ai.ai_in_multiplayer);
34}
35
36/* static */ void AI::StartNew(CompanyID company)
37{
38 assert(Company::IsValidID(company));
39
40 /* Clients shouldn't start AIs */
41 if (_networking && !_network_server) return;
42
43 AutoRestoreBackup cur_company(_current_company, company);
44 Company *c = Company::Get(company);
45
46 AIConfig *config = c->ai_config.get();
47 if (config == nullptr) {
48 c->ai_config = std::make_unique<AIConfig>(*AIConfig::GetConfig(company, AIConfig::ScriptSettingSource::ForceCurrentGame));
49 config = c->ai_config.get();
50 }
51
52 AIInfo *info = config->GetInfo();
53 if (info == nullptr) {
54 info = AI::scanner_info->SelectRandomAI();
55 assert(info != nullptr);
56 /* Load default data and store the name in the settings */
57 config->Change(info->GetName(), -1, false);
58 }
60
61 c->ai_info = info;
62 assert(c->ai_instance == nullptr);
63 c->ai_instance = std::make_unique<AIInstance>();
64 c->ai_instance->Initialize(info);
65 c->ai_instance->LoadOnStack(config->GetToLoadData());
66 config->SetToLoadData(nullptr);
67
69 return;
70}
71
72/* static */ void AI::GameLoop()
73{
74 /* If we are in networking, only servers run this function, and that only if it is allowed */
75 if (_networking && (!_network_server || !_settings_game.ai.ai_in_multiplayer)) return;
76
77 /* The speed with which AIs go, is limited by the 'competitor_speed' */
79 assert(_settings_game.difficulty.competitor_speed <= 4);
80 if ((AI::frame_counter & ((1 << (4 - _settings_game.difficulty.competitor_speed)) - 1)) != 0) return;
81
82 for (const Company *c : Company::Iterate()) {
83 if (c->is_ai) {
84 PerformanceMeasurer framerate((PerformanceElement)(PFE_AI0 + c->index));
85 AutoRestoreBackup cur_company(_current_company, c->index);
86 c->ai_instance->GameLoop();
87 /* Occasionally collect garbage; every 255 ticks do one company.
88 * Effectively collecting garbage once every two months per AI. */
89 if ((AI::frame_counter & 255) == 0 && (CompanyID)GB(AI::frame_counter, 8, 4) == c->index) {
90 c->ai_instance->CollectGarbage();
91 }
92 } else {
94 }
95 }
96}
97
98/* static */ uint AI::GetTick()
99{
100 return AI::frame_counter;
101}
102
103/* static */ void AI::Stop(CompanyID company)
104{
105 if (_networking && !_network_server) return;
107
108 AutoRestoreBackup cur_company(_current_company, company);
109 Company *c = Company::Get(company);
110
111 c->ai_instance.reset();
112 c->ai_info = nullptr;
113 c->ai_config.reset();
114
116}
117
118/* static */ void AI::Pause(CompanyID company)
119{
120 /* The reason why dedicated servers are forbidden to execute this
121 * command is not because it is unsafe, but because there is no way
122 * for the server owner to unpause the script again. */
123 if (_network_dedicated) return;
124
125 AutoRestoreBackup cur_company(_current_company, company);
126 Company::Get(company)->ai_instance->Pause();
127}
128
129/* static */ void AI::Unpause(CompanyID company)
130{
131 AutoRestoreBackup cur_company(_current_company, company);
132 Company::Get(company)->ai_instance->Unpause();
133}
134
135/* static */ bool AI::IsPaused(CompanyID company)
136{
137 AutoRestoreBackup cur_company(_current_company, company);
138 return Company::Get(company)->ai_instance->IsPaused();
139}
140
141/* static */ void AI::KillAll()
142{
143 /* It might happen there are no companies .. than we have nothing to loop */
144 if (Company::GetPoolSize() == 0) return;
145
146 for (const Company *c : Company::Iterate()) {
147 if (c->is_ai) AI::Stop(c->index);
148 }
149}
150
151/* static */ void AI::Initialize()
152{
153 if (AI::scanner_info != nullptr) AI::Uninitialize(true);
154
156 if (AI::scanner_info == nullptr) {
158 AI::scanner_info = std::make_unique<AIScannerInfo>();
159 AI::scanner_info->Initialize();
160 AI::scanner_library = std::make_unique<AIScannerLibrary>();
161 AI::scanner_library->Initialize();
162 }
163}
164
165/* static */ void AI::Uninitialize(bool keepConfig)
166{
167 AI::KillAll();
168
169 if (keepConfig) {
170 /* Run a rescan, which indexes all AIInfos again, and check if we can
171 * still load all the AIS, while keeping the configs in place */
172 Rescan();
173 } else {
174 AI::scanner_info.reset();
175 AI::scanner_library.reset();
176
177 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
178 _settings_game.script_config.ai[c].reset();
179 _settings_newgame.script_config.ai[c].reset();
180 }
181 }
182}
183
184/* static */ void AI::ResetConfig()
185{
186 /* Check for both newgame as current game if we can reload the AIInfo inside
187 * the AIConfig. If not, remove the AI from the list (which will assign
188 * a random new AI on reload). */
189 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
190 if (_settings_game.script_config.ai[c] != nullptr && _settings_game.script_config.ai[c]->HasScript()) {
191 if (!_settings_game.script_config.ai[c]->ResetInfo(true)) {
192 Debug(script, 0, "After a reload, the AI by the name '{}' was no longer found, and removed from the list.", _settings_game.script_config.ai[c]->GetName());
193 _settings_game.script_config.ai[c]->Change(std::nullopt);
194 }
195 }
196
197 if (_settings_newgame.script_config.ai[c] != nullptr && _settings_newgame.script_config.ai[c]->HasScript()) {
198 if (!_settings_newgame.script_config.ai[c]->ResetInfo(false)) {
199 Debug(script, 0, "After a reload, the AI by the name '{}' was no longer found, and removed from the list.", _settings_newgame.script_config.ai[c]->GetName());
200 _settings_newgame.script_config.ai[c]->Change(std::nullopt);
201 }
202 }
203
204 if (Company::IsValidAiID(c) && Company::Get(c)->ai_config != nullptr) {
205 AIConfig *config = Company::Get(c)->ai_config.get();
206 if (!config->ResetInfo(true)) {
207 /* The code belonging to an already running AI was deleted. We can only do
208 * one thing here to keep everything sane and that is kill the AI. After
209 * killing the offending AI we start a random other one in it's place, just
210 * like what would happen if the AI was missing during loading. */
211 AI::Stop(c);
212 AI::StartNew(c);
213 } else {
214 /* Update the reference in the Company struct. */
215 Company::Get(c)->ai_info = config->GetInfo();
216 }
217 }
218 }
219}
220
221/* static */ void AI::NewEvent(CompanyID company, ScriptEvent *event)
222{
223 ScriptObjectRef counter(event);
224
225 /* Clients should ignore events */
227 return;
228 }
229
230 /* Only AIs can have an event-queue */
231 if (!Company::IsValidAiID(company)) {
232 return;
233 }
234
235 /* Queue the event */
236 AutoRestoreBackup cur_company(_current_company, company);
237 Company::Get(_current_company)->ai_instance->InsertEvent(event);
238}
239
240/* static */ void AI::BroadcastNewEvent(ScriptEvent *event, CompanyID skip_company)
241{
242 ScriptObjectRef counter(event);
243
244 /* Clients should ignore events */
246 return;
247 }
248
249 /* Try to send the event to all AIs */
250 for (CompanyID c = CompanyID::Begin(); c < MAX_COMPANIES; ++c) {
251 if (c != skip_company) AI::NewEvent(c, event);
252 }
253}
254
255/* static */ void AI::Save(CompanyID company)
256{
258 Company *c = Company::GetIfValid(company);
259 assert(c != nullptr);
260
261 /* When doing emergency saving, an AI can be not fully initialised. */
262 if (c->ai_instance != nullptr) {
263 AutoRestoreBackup cur_company(_current_company, company);
264 c->ai_instance->Save();
265 return;
266 }
267 }
268
270}
271
272/* static */ void AI::GetConsoleList(std::back_insert_iterator<std::string> &output_iterator, bool newest_only)
273{
274 AI::scanner_info->GetConsoleList(output_iterator, newest_only);
275}
276
277/* static */ void AI::GetConsoleLibraryList(std::back_insert_iterator<std::string> &output_iterator, bool newest_only)
278{
279 AI::scanner_library->GetConsoleList(output_iterator, newest_only);
280}
281
282/* static */ const ScriptInfoList *AI::GetInfoList()
283{
284 return AI::scanner_info->GetInfoList();
285}
286
288{
289 return AI::scanner_info->GetUniqueInfoList();
290}
291
292/* static */ AIInfo *AI::FindInfo(const std::string &name, int version, bool force_exact_match)
293{
294 return AI::scanner_info->FindInfo(name, version, force_exact_match);
295}
296
297/* static */ AILibrary *AI::FindLibrary(const std::string &library, int version)
298{
299 return AI::scanner_library->FindLibrary(library, version);
300}
301
314
321/* static */ bool AI::HasAI(const ContentInfo &ci, bool md5sum)
322{
323 return AI::scanner_info->HasScript(ci, md5sum);
324}
325
332/* static */ bool AI::HasAILibrary(const ContentInfo &ci, bool md5sum)
333{
334 return AI::scanner_library->HasScript(ci, md5sum);
335}
336
342{
343 return AI::scanner_info.get();
344}
345
351{
352 return AI::scanner_library.get();
353}
354
Base functions for all AIs.
AIConfig stores the configuration settings of every AI.
AIInfo keeps track of all information of an AI, like Author, Description, ...
The AIInstance tracks an AI.
Declarations of the class for AI scanner.
Class for backupping variables and making sure they are restored later.
Functions related to bit mathematics.
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
AI instantion of script configuration.
Definition ai_config.hpp:17
class AIInfo * GetInfo() const
Get the ScriptInfo linked to this ScriptConfig.
Definition ai_config.cpp:37
bool ResetInfo(bool force_exact_match)
When ever the AI Scanner is reloaded, all infos become invalid.
Definition ai_config.cpp:47
static AIConfig * GetConfig(CompanyID company, ScriptSettingSource source=ScriptSettingSource::Default)
Get the AI configuration of specific company.
Definition ai_config.cpp:20
All static information from an AI like name, version, etc.
Definition ai_info.hpp:16
All static information from an AI library like name, version, etc.
Definition ai_info.hpp:69
AI instantiation of a ScriptScanner.
AI instantiation of a ScriptScanner for libraries.
static uint GetTick()
Get the current AI tick.
Definition ai_core.cpp:98
static std::unique_ptr< AIScannerLibrary > scanner_library
ScriptScanner instance that is used to find AI Libraries.
Definition ai.hpp:149
static void GetConsoleLibraryList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only)
Get the list of registered scripts to print on the console.
Definition ai_core.cpp:277
static void Uninitialize(bool keepConfig)
Uninitialize the AI system.
Definition ai_core.cpp:165
static AIScannerLibrary * GetScannerLibrary()
Gets the ScriptScanner instance that is used to find AI Libraries.
Definition ai_core.cpp:350
static void Pause(CompanyID company)
Suspend the AI and then pause execution of the script.
Definition ai_core.cpp:118
static void Initialize()
Initialize the AI system.
Definition ai_core.cpp:151
static bool HasAILibrary(const ContentInfo &ci, bool md5sum)
Check whether we have an AI library with the exact characteristics as ci.
Definition ai_core.cpp:332
static void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only)
Get the list of registered scripts to print on the console.
Definition ai_core.cpp:272
static void BroadcastNewEvent(ScriptEvent *event, CompanyID skip_company=CompanyID::Invalid())
Broadcast a new event to all active AIs.
Definition ai_core.cpp:240
static std::unique_ptr< AIScannerInfo > scanner_info
ScriptScanner instance that is used to find AIs.
Definition ai.hpp:148
static void GameLoop()
Called every game-tick to let AIs do something.
Definition ai_core.cpp:72
static bool CanStartNew()
Is it possible to start a new AI company?
Definition ai_core.cpp:30
static void StartNew(CompanyID company)
Start a new AI company.
Definition ai_core.cpp:36
static void Stop(CompanyID company)
Stop a company to be controlled by an AI.
Definition ai_core.cpp:103
static class AILibrary * FindLibrary(const std::string &library, int version)
Find a library.
Definition ai_core.cpp:297
static void ResetConfig()
Reset all AIConfigs, and make them reload their AIInfo.
Definition ai_core.cpp:184
static void NewEvent(CompanyID company, ScriptEvent *event)
Queue a new event for an AI.
Definition ai_core.cpp:221
static AIScannerInfo * GetScannerInfo()
Gets the ScriptScanner instance that is used to find AIs.
Definition ai_core.cpp:341
static const ScriptInfoList * GetUniqueInfoList()
Get the list of the latest version of all registered scripts.
Definition ai_core.cpp:287
static bool HasAI(const ContentInfo &ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:321
static void Rescan()
Rescans all searchpaths for available AIs.
Definition ai_core.cpp:302
static uint frame_counter
Tick counter for the AI code.
Definition ai.hpp:147
static bool IsPaused(CompanyID company)
Checks if the AI is paused.
Definition ai_core.cpp:135
static void Save(CompanyID company)
Save data from an AI to a savegame.
Definition ai_core.cpp:255
static void Unpause(CompanyID company)
Resume execution of the AI.
Definition ai_core.cpp:129
static void KillAll()
Kill any and all AIs we manage.
Definition ai_core.cpp:141
static const ScriptInfoList * GetInfoList()
Get the list of all registered scripts.
Definition ai_core.cpp:282
static class AIInfo * FindInfo(const std::string &name, int version, bool force_exact_match)
Finds the appropriate ScriptInfo for a given script name and version.
Definition ai_core.cpp:292
RAII class for measuring simple elements of performance.
static void SetInactive(PerformanceElement elem)
Mark a performance element as not currently in use.
@ ForceCurrentGame
Get the Script config from the current game.
void AnchorUnchangeableSettings()
As long as the default of a setting has not been changed, the value of the setting is not stored.
void Change(std::optional< std::string_view > name, int version=-1, bool force_exact_match=false)
Set another Script to be loaded in this slot.
const std::string & GetName() const
Get the Name of the script.
static void SaveEmpty()
Don't save any data in the savegame.
@ AI
Scan for AIs and its libraries.
Definition fileio_func.h:66
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition fileio.cpp:383
Definition of stuff that is very close to a company, like the company struct itself.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Types for recording game performance data.
PerformanceElement
Elements of game performance that can be measured.
@ PFE_AI0
AI execution for player slot 1.
bool _networking
are we in networking mode?
Definition network.cpp:67
bool _network_dedicated
are we a dedicated server?
Definition network.cpp:70
bool _network_server
network-server is active
Definition network.cpp:68
Basic functions/variables used all over the place.
A number of safeguards to prevent using unsafe methods.
std::map< std::string, class ScriptInfo *, CaseInsensitiveComparator > ScriptInfoList
Type for the list of scripts.
GameSettings _settings_game
Game settings of a running game or the scenario editor.
Definition settings.cpp:61
GameSettings _settings_newgame
Game settings for new games (updated from the intro screen).
Definition settings.cpp:62
Definition of base types and functions in a cross-platform compatible way.
Class to backup a specific variable and restore it upon destruction of this object to prevent stack v...
static bool IsValidAiID(auto index)
Is this company a valid company, controlled by the computer (a NoAI program)?
Container for all important information about a piece of content.
static Pool::IterateWrapper< Company > Iterate(size_t from=0)
static Company * Get(auto index)
static Company * GetIfValid(auto index)
void SetWindowClassesDirty(WindowClass cls)
Mark all windows of a particular class as dirty (in need of repainting).
Definition window.cpp:3230
void InvalidateWindowData(WindowClass cls, WindowNumber number, int data, bool gui_scope)
Mark window data of the window of a given class and specific window number as invalid (in need of re-...
Definition window.cpp:3322
void InvalidateWindowClassesData(WindowClass cls, int data, bool gui_scope)
Mark window data of all windows of a given class as invalid (in need of re-computing) Note that by de...
Definition window.cpp:3340
Window functions not directly related to making/drawing windows.
@ WC_SCRIPT_SETTINGS
Script settings; Window numbers:
@ WC_SCRIPT_LIST
Scripts list; Window numbers:
@ WC_SCRIPT_DEBUG
Script debug window; Window numbers: