OpenTTD Source 20241224-master-gf74b0cf984
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 <http://www.gnu.org/licenses/>.
6 */
7
10#include "../stdafx.h"
11#include "../core/backup_type.hpp"
12#include "../core/bitmath_func.hpp"
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 */ AIScannerInfo *AI::scanner_info = nullptr;
28/* static */ 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 */
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 Backup<CompanyID> 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::SSS_FORCE_GAME));
49 config = c->ai_config.get();
50 }
51
52 AIInfo *info = config->GetInfo();
53 if (info == nullptr) {
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
68 cur_company.Restore();
69
71 return;
72}
73
74/* static */ void AI::GameLoop()
75{
76 /* If we are in networking, only servers run this function, and that only if it is allowed */
78
79 /* The speed with which AIs go, is limited by the 'competitor_speed' */
82 if ((AI::frame_counter & ((1 << (4 - _settings_game.difficulty.competitor_speed)) - 1)) != 0) return;
83
85 for (const Company *c : Company::Iterate()) {
86 if (c->is_ai) {
87 PerformanceMeasurer framerate((PerformanceElement)(PFE_AI0 + c->index));
88 cur_company.Change(c->index);
89 c->ai_instance->GameLoop();
90 /* Occasionally collect garbage; every 255 ticks do one company.
91 * Effectively collecting garbage once every two months per AI. */
92 if ((AI::frame_counter & 255) == 0 && (CompanyID)GB(AI::frame_counter, 8, 4) == c->index) {
93 c->ai_instance->CollectGarbage();
94 }
95 } else {
97 }
98 }
99 cur_company.Restore();
100}
101
102/* static */ uint AI::GetTick()
103{
104 return AI::frame_counter;
105}
106
107/* static */ void AI::Stop(CompanyID company)
108{
109 if (_networking && !_network_server) return;
111
112 Backup<CompanyID> cur_company(_current_company, company);
113 Company *c = Company::Get(company);
114
115 c->ai_instance.reset();
116 c->ai_info = nullptr;
117 c->ai_config.reset();
118
119 cur_company.Restore();
120
122}
123
124/* static */ void AI::Pause(CompanyID company)
125{
126 /* The reason why dedicated servers are forbidden to execute this
127 * command is not because it is unsafe, but because there is no way
128 * for the server owner to unpause the script again. */
129 if (_network_dedicated) return;
130
131 Backup<CompanyID> cur_company(_current_company, company);
132 Company::Get(company)->ai_instance->Pause();
133
134 cur_company.Restore();
135}
136
137/* static */ void AI::Unpause(CompanyID company)
138{
139 Backup<CompanyID> cur_company(_current_company, company);
140 Company::Get(company)->ai_instance->Unpause();
141
142 cur_company.Restore();
143}
144
145/* static */ bool AI::IsPaused(CompanyID company)
146{
147 Backup<CompanyID> cur_company(_current_company, company);
148 bool paused = Company::Get(company)->ai_instance->IsPaused();
149
150 cur_company.Restore();
151
152 return paused;
153}
154
155/* static */ void AI::KillAll()
156{
157 /* It might happen there are no companies .. than we have nothing to loop */
158 if (Company::GetPoolSize() == 0) return;
159
160 for (const Company *c : Company::Iterate()) {
161 if (c->is_ai) AI::Stop(c->index);
162 }
163}
164
165/* static */ void AI::Initialize()
166{
167 if (AI::scanner_info != nullptr) AI::Uninitialize(true);
168
170 if (AI::scanner_info == nullptr) {
173 AI::scanner_info->Initialize();
175 AI::scanner_library->Initialize();
176 }
177}
178
179/* static */ void AI::Uninitialize(bool keepConfig)
180{
181 AI::KillAll();
182
183 if (keepConfig) {
184 /* Run a rescan, which indexes all AIInfos again, and check if we can
185 * still load all the AIS, while keeping the configs in place */
186 Rescan();
187 } else {
188 delete AI::scanner_info;
189 delete AI::scanner_library;
190 AI::scanner_info = nullptr;
191 AI::scanner_library = nullptr;
192
193 for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
194 if (_settings_game.ai_config[c] != nullptr) {
195 delete _settings_game.ai_config[c];
196 _settings_game.ai_config[c] = nullptr;
197 }
198 if (_settings_newgame.ai_config[c] != nullptr) {
200 _settings_newgame.ai_config[c] = nullptr;
201 }
202 }
203 }
204}
205
206/* static */ void AI::ResetConfig()
207{
208 /* Check for both newgame as current game if we can reload the AIInfo inside
209 * the AIConfig. If not, remove the AI from the list (which will assign
210 * a random new AI on reload). */
211 for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
212 if (_settings_game.ai_config[c] != nullptr && _settings_game.ai_config[c]->HasScript()) {
213 if (!_settings_game.ai_config[c]->ResetInfo(true)) {
214 Debug(script, 0, "After a reload, the AI by the name '{}' was no longer found, and removed from the list.", _settings_game.ai_config[c]->GetName());
215 _settings_game.ai_config[c]->Change(std::nullopt);
216 }
217 }
218
220 if (!_settings_newgame.ai_config[c]->ResetInfo(false)) {
221 Debug(script, 0, "After a reload, the AI by the name '{}' was no longer found, and removed from the list.", _settings_newgame.ai_config[c]->GetName());
222 _settings_newgame.ai_config[c]->Change(std::nullopt);
223 }
224 }
225
226 if (Company::IsValidAiID(c) && Company::Get(c)->ai_config != nullptr) {
227 AIConfig *config = Company::Get(c)->ai_config.get();
228 if (!config->ResetInfo(true)) {
229 /* The code belonging to an already running AI was deleted. We can only do
230 * one thing here to keep everything sane and that is kill the AI. After
231 * killing the offending AI we start a random other one in it's place, just
232 * like what would happen if the AI was missing during loading. */
233 AI::Stop(c);
234 AI::StartNew(c);
235 } else {
236 /* Update the reference in the Company struct. */
237 Company::Get(c)->ai_info = config->GetInfo();
238 }
239 }
240 }
241}
242
243/* static */ void AI::NewEvent(CompanyID company, ScriptEvent *event)
244{
245 ScriptObjectRef counter(event);
246
247 /* Clients should ignore events */
249 return;
250 }
251
252 /* Only AIs can have an event-queue */
253 if (!Company::IsValidAiID(company)) {
254 return;
255 }
256
257 /* Queue the event */
258 Backup<CompanyID> cur_company(_current_company, company);
259 Company::Get(_current_company)->ai_instance->InsertEvent(event);
260 cur_company.Restore();
261}
262
263/* static */ void AI::BroadcastNewEvent(ScriptEvent *event, CompanyID skip_company)
264{
265 ScriptObjectRef counter(event);
266
267 /* Clients should ignore events */
269 return;
270 }
271
272 /* Try to send the event to all AIs */
273 for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
274 if (c != skip_company) AI::NewEvent(c, event);
275 }
276}
277
278/* static */ void AI::Save(CompanyID company)
279{
281 Company *c = Company::GetIfValid(company);
282 assert(c != nullptr);
283
284 /* When doing emergency saving, an AI can be not fully initialised. */
285 if (c->ai_instance != nullptr) {
286 Backup<CompanyID> cur_company(_current_company, company);
287 c->ai_instance->Save();
288 cur_company.Restore();
289 return;
290 }
291 }
292
294}
295
296/* static */ void AI::GetConsoleList(std::back_insert_iterator<std::string> &output_iterator, bool newest_only)
297{
298 AI::scanner_info->GetConsoleList(output_iterator, newest_only);
299}
300
301/* static */ void AI::GetConsoleLibraryList(std::back_insert_iterator<std::string> &output_iterator)
302{
303 AI::scanner_library->GetConsoleList(output_iterator, true);
304}
305
306/* static */ const ScriptInfoList *AI::GetInfoList()
307{
309}
310
312{
314}
315
316/* static */ AIInfo *AI::FindInfo(const std::string &name, int version, bool force_exact_match)
317{
318 return AI::scanner_info->FindInfo(name, version, force_exact_match);
319}
320
321/* static */ AILibrary *AI::FindLibrary(const std::string &library, int version)
322{
323 return AI::scanner_library->FindLibrary(library, version);
324}
325
338
345/* static */ bool AI::HasAI(const ContentInfo *ci, bool md5sum)
346{
347 return AI::scanner_info->HasScript(ci, md5sum);
348}
349
350/* static */ bool AI::HasAILibrary(const ContentInfo *ci, bool md5sum)
351{
352 return AI::scanner_library->HasScript(ci, md5sum);
353}
354
356{
357 return AI::scanner_info;
358}
359
361{
362 return AI::scanner_library;
363}
364
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
debug_inline static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
static AIConfig * GetConfig(CompanyID company, ScriptSettingSource source=SSS_DEFAULT)
Get the config of a company.
Definition ai_config.cpp:20
bool ResetInfo(bool force_exact_match)
When ever the AI Scanner is reloaded, all infos become invalid.
Definition ai_config.cpp:48
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:57
class AIInfo * FindInfo(const std::string &name, int version, bool force_exact_match)
Check if we have an AI by name and version available in our list.
class AIInfo * SelectRandomAI() const
Select a random AI.
class AILibrary * FindLibrary(const std::string &library, int version)
Find a library in the pool.
static uint GetTick()
Get the current AI tick.
Definition ai_core.cpp:102
static class AIScannerInfo * scanner_info
ScriptScanner instance that is used to find AIs.
Definition ai.hpp:142
static void Uninitialize(bool keepConfig)
Uninitialize the AI system.
Definition ai_core.cpp:179
static AIScannerLibrary * GetScannerLibrary()
Gets the ScriptScanner instance that is used to find AI Libraries.
Definition ai_core.cpp:360
static bool HasAI(const struct ContentInfo *ci, bool md5sum)
Wrapper function for AIScanner::HasAI.
Definition ai_core.cpp:345
static void Pause(CompanyID company)
Suspend the AI and then pause execution of the script.
Definition ai_core.cpp:124
static void GetConsoleLibraryList(std::back_insert_iterator< std::string > &output_iterator)
Wrapper function for AIScanner::GetAIConsoleLibraryList.
Definition ai_core.cpp:301
static void Initialize()
Initialize the AI system.
Definition ai_core.cpp:165
static class AIScannerLibrary * scanner_library
ScriptScanner instance that is used to find AI Libraries.
Definition ai.hpp:143
static void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only)
Wrapper function for AIScanner::GetAIConsoleList.
Definition ai_core.cpp:296
static void GameLoop()
Called every game-tick to let AIs do something.
Definition ai_core.cpp:74
static bool CanStartNew()
Is it possible to start a new AI company?
Definition ai_core.cpp:30
static void BroadcastNewEvent(ScriptEvent *event, CompanyID skip_company=MAX_COMPANIES)
Broadcast a new event to all active AIs.
Definition ai_core.cpp:263
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:107
static class AILibrary * FindLibrary(const std::string &library, int version)
Wrapper function for AIScanner::FindLibrary.
Definition ai_core.cpp:321
static void ResetConfig()
Reset all AIConfigs, and make them reload their AIInfo.
Definition ai_core.cpp:206
static void NewEvent(CompanyID company, ScriptEvent *event)
Queue a new event for an AI.
Definition ai_core.cpp:243
static AIScannerInfo * GetScannerInfo()
Gets the ScriptScanner instance that is used to find AIs.
Definition ai_core.cpp:355
static const ScriptInfoList * GetUniqueInfoList()
Wrapper function for AIScanner::GetUniqueAIInfoList.
Definition ai_core.cpp:311
static void Rescan()
Rescans all searchpaths for available AIs.
Definition ai_core.cpp:326
static uint frame_counter
Tick counter for the AI code.
Definition ai.hpp:141
static bool IsPaused(CompanyID company)
Checks if the AI is paused.
Definition ai_core.cpp:145
static void Save(CompanyID company)
Save data from an AI to a savegame.
Definition ai_core.cpp:278
static void Unpause(CompanyID company)
Resume execution of the AI.
Definition ai_core.cpp:137
static void KillAll()
Kill any and all AIs we manage.
Definition ai_core.cpp:155
static const ScriptInfoList * GetInfoList()
Wrapper function for AIScanner::GetAIInfoList.
Definition ai_core.cpp:306
static class AIInfo * FindInfo(const std::string &name, int version, bool force_exact_match)
Wrapper function for AIScanner::FindInfo.
Definition ai_core.cpp:316
RAII class for measuring simple elements of performance.
static void SetInactive(PerformanceElement elem)
Mark a performance element as not currently in use.
@ SSS_FORCE_GAME
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.
bool HasScript() const
Is this config attached to an Script? In other words, is there a Script that is assigned to this slot...
void Change(std::optional< const std::string > 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.
const std::string & GetName() const
Get the Name of the script.
static void SaveEmpty()
Don't save any data in the savegame.
const ScriptInfoList * GetInfoList()
Get the list of all registered scripts.
bool HasScript(const struct ContentInfo *ci, bool md5sum)
Check whether we have a script with the exact characteristics as ci.
const ScriptInfoList * GetUniqueInfoList()
Get the list of the latest version of all registered scripts.
void RescanDir()
Rescan the script dir.
void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only) const
Get the list of registered scripts to print on the console.
uint DoScan(Subdirectory sd)
Perform the scanning of a particular subdirectory.
Definition fileio.cpp:375
@ AI
Scan for AIs and its libraries.
Definition fileio_func.h:67
CompanyID _current_company
Company currently doing an action.
Owner
Enum for all companies/owners.
@ COMPANY_FIRST
First company, same as owner.
@ MAX_COMPANIES
Maximum number of companies.
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition debug.h:37
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:65
bool _network_dedicated
are we a dedicated server?
Definition network.cpp:68
bool _network_server
network-server is active
Definition network.cpp:66
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:57
GameSettings _settings_newgame
Game settings for new games (updated from the intro screen).
Definition settings.cpp:58
bool ai_in_multiplayer
so we allow AIs in multiplayer
Class to backup a specific variable and restore it later.
void Change(const U &new_value)
Change the value of the variable.
void Restore()
Restore the variable.
static bool IsValidAiID(size_t index)
Is this company a valid company, controlled by the computer (a NoAI program)?
Container for all important information about a piece of content.
uint8_t competitor_speed
the speed at which the AI builds
class AIConfig * ai_config[MAX_COMPANIES]
settings per company
AISettings ai
what may the AI do?
DifficultySettings difficulty
settings related to the difficulty
static size_t GetPoolSize()
Returns first unused index.
static bool IsValidID(size_t index)
Tests whether given index can be used to get valid (non-nullptr) Titem.
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.
static Titem * GetIfValid(size_t index)
Returns Titem with given index.
static Titem * Get(size_t index)
Returns Titem with given index.
void SetWindowClassesDirty(WindowClass cls)
Mark all windows of a particular class as dirty (in need of repainting)
Definition window.cpp:3127
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:3219
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:3236
@ WC_SCRIPT_SETTINGS
Script settings; Window numbers:
@ WC_SCRIPT_LIST
Scripts list; Window numbers:
@ WC_SCRIPT_DEBUG
Script debug window; Window numbers: