Research target:
G:\GitHub\OpenTTD-patches(jgrpp source workspace) Reference resources:G:\game\openttd-jgrpp(compiled full game, read-only) Research date: 2026-08-14 Branch: jgrpp (current HEAD5b5c452e1b, approximately version 16.0)
1. Project Overview
OpenTTD jgrpp (JGR's Patchpack) is a well-known enhanced fork based on OpenTTD. The source code in this workspace corresponds to version 16.0, containing many JGR-specific features (tracerestrict, programmable signals, scheduled dispatch, template replacement, enhanced signals, etc.).
| Item | Description |
|---|---|
| Language | C++20 (CMAKE_CXX_STANDARD 20, no extensions) |
| Build | CMake (≥3.17), src/CMakeLists.txt organized by directory |
| Script Engine | Squirrel (src/3rdparty/squirrel/), used for AI / GameScript / template scripts |
| Network | Custom TCP/UDP protocol layer (src/network/), HTTP using WinHttp (Windows) / libcurl (others) / JS (Emscripten) |
| Save | src/saveload/ + src/sl/ custom binary format |
| Threading | src/thread.h + src/worker_thread.cpp (WorkerThreadPool task pool), src/timer/ timers |
2. Source Directory Structure and Module Organization
Top level: CMakeLists.txt is the root build script; src/ contains all C++ source code; bin/ contains runtime data (AI scripts, language files, etc.); media/, os/, cmake/, docs/ contain resources, platform code, build scripts, and documentation respectively.
Main subdirectories within src/ and their responsibilities:
| Directory | Responsibility |
|---|---|
src/core/ | Basic utilities: pool types, bitsets, containers, thread wrappers, bit operations, etc. |
src/network/ | Networking: server/client/UDP/HTTP/content download/admin protocol (core/ subdirectory for protocol basics) |
src/script/ | Script runtime framework: Squirrel wrapper, instances, configuration; api/ contains all API classes exposed to AI/GS |
src/ai/ | NoAI framework (AI instances, scanner, configuration, GUI) |
src/game/ | GameScript framework (GS instances, configuration, GUI) |
src/newgrf/ | NewGRF decoding and processing |
src/pathfinder/ | Pathfinding (YAPF, NPF) |
src/saveload/, src/sl/ | Save/load |
src/blitter/, src/video/, src/fontcache/, src/music/, src/sound/ | Rendering/audio/video backends |
src/lang/ | Localization string sources (generated by strgen) |
src/table/ | Static tables; table/settings/*.ini are setting definition sources (generated by settingsgen) |
src/3rdparty/ | Third-party libraries (squirrel, llvm, icu, etc.) |
src/timer/, src/os/, src/misc/ | Timers, platform, miscellaneous |
Top-level scattered files are named by system (e.g., rail_gui.cpp, group_cmd.cpp, vehicle.cpp, order_cmd.cpp, economy.cpp), following OpenTTD conventions: *_cmd for command logic, *_gui for windows, *_base/_type/_func for data structures and inline functions.
3. Build Method
- CMake three steps:
cmake -B build ..→cmake --build build→ Outputopenttd.exe. The repository includesbuild.sh/build-dedicated.sh. - Dependencies: Declared in
vcpkg.json(zlib, lzma, lzo, zstd, png, SDL2, freetype, harfbuzz, icu, opus, etc.); Windows uses WinHttp (no curl needed), non-Windows uses libcurl (CMakeLists.txt:121-127). - Host tools:
strgen(language files),settingsgen(generates setting code fromsrc/table/settings/*.ini),squirrel_export(generates Squirrel bindings fromsrc/script/api/script_*.hpp). - Key generated files:
generated/script/api/<ai|gs>/...sq.hpp(API bindings, automatically discovered byfile(GLOB script_*.hpp), new API classes do not need to modify registration lists, just add.hppand include.cppinsrc/script/api/CMakeLists.txtsource list);generated/rev.cpp;generated/ottdres.rc. - Settings system: The modern version has been changed to INI-driven —
src/table/settings/*.ini(containing[SDTC_VAR]sections,cat=SC_*categories,flags,post_cb, etc.), settingsgen generatessettings_*.cpp/h; corresponding struct members are insrc/settings_type.h(e.g.,NetworkSettingsstarting at line 575).
4. Five Feature System Implementation Locations
4.1 Resource Download (Content Download / BaNaNaSplit)
| Point of Interest | Location |
|---|---|
| Content client main class | src/network/network_content.h/.cpp — ClientNetworkContentSocketHandler (also ContentCallback + HTTPCallback) |
| Content download GUI | src/network/network_content_gui.cpp/.h |
| HTTP client | src/network/core/http.h/.cpp — NetworkHTTPSocketHandler::Connect(uri, callback, data), async event-driven (non-blocking, main loop polling) |
| Content server connection string | src/network/core/config.cpp — NetworkContentServerConnectionString(): environment variable OTTD_CONTENT_SERVER_CS, default content.openttd.org (TCP metadata protocol) |
| Mirror URI | src/network/core/config.cpp — NetworkContentMirrorUriString(): environment variable OTTD_CONTENT_MIRROR_URI, default https://binaries.openttd.org/bananas |
| Download flow | DownloadSelectedContent() → DownloadSelectedContentHTTP() (POST all content IDs to mirror, mirror returns a multi-file tar stream, written file by file) → AfterDownload() does gunzip + TarScanner extraction |
| Decompression | GunzipFile() (zlib), TarScanner/ExtractTar (src/tar_type.h / src/fileio.cpp) |
| Threading facilities (usable for parallelism) | src/worker_thread.h/.cpp — WorkerThreadPool + EnqueueJob; src/thread.h is a platform thread wrapper |
Current conclusion: ① Only 1 mirror, configurable only via environment variable, no in-game setting; ② Download is single-connection, serial (one POST for all files); ③ Decompression is synchronous on the main thread. → Multi-threaded/multi-mirror modification points are clear.
4.2 Server Online Player / Company Limits
| Point of Interest | Location |
|---|---|
| Client limit constant | src/network/network_type.h:21 — static const uint MAX_CLIENTS = 255; |
| Client pool | Same file ClientPoolIDTag : PoolIDTraits<uint16_t, MAX_CLIENTS + 1, 0xFFFF>; ClientID is uint32_t |
| Company ID pool | src/company_type.h — CompanyIDTag : PoolIDTraits<uint8_t, 0xF, 0xFF> → MAX_COMPANIES = CompanyID::End().base() = 15; fake companies occupy 253/254/255 |
| Company mask | Same file CompanyMask : BaseBitSet<CompanyMask, CompanyID, uint16_t> (16-bit, can only track 16 companies) |
| Server acceptance check | src/network/network_server.cpp:360 — _network_clients_connected < MAX_CLIENTS; static_assert(NetworkClientSocketPool::MAX_SIZE == MAX_CLIENTS + 1) |
| Client settings | src/table/settings/network_settings.ini:231/241 — network.max_companies (def 15, max MAX_COMPANIES), network.max_clients (def 25, max MAX_CLIENTS); struct in src/settings_type.h NetworkSettings |
| Protocol bit width (hard constraint) | src/network/core/network_game_info.cpp — companies_max and clients_max are both sent as Send_uint8/Recv_uint8 (lines 251-296, 422-432) |
| Server list display | src/network/network_gui.cpp:519 etc. |
Current conclusion:
- Client limit = 255 is already the protocol limit (uint8 field + Game Coordinator/server browser compatibility). To break through,
network_game_inforelated fields must be changed to uint16 (full client↔server chain + UDP broadcast + Game Coordinator protocol), which is an external protocol change. - Company limit = 15 (
CompanyIDTag's End=0xF). Can be safely raised to 252 (End=0xFC): underlying type is still uint8, save byte width unchanged (compatible with old saves), need to simultaneously widenCompanyMask(uint16→uint32) and interface/loop assumptions.
4.3 Vehicle Grouping
| Point of Interest | Location |
|---|---|
| Group data structure | src/group.h — Group : GroupPool::PoolItem (name/owner/vehicle_type/flags/livery/statistics/parent/number); GroupID, DEFAULT_GROUP, IsDefaultGroupID/IsAllGroupID/IsTopLevelGroupID |
| Group commands | src/group_cmd.cpp — CmdCreateGroup(536), CmdDeleteGroup(585), CmdAlterGroup(646), CmdAddVehicleGroup, CmdAddSharedVehicleGroup (adds shared-order vehicles to an existing group, around line 718 AddVehicleToGroup) |
| Command registration | src/group_cmd.h:27-35 — DEF_CMD_TUPLE_NT(Commands::XXX, CmdXXX, {}, CommandType::RouteManagement, CmdDataT<...>); enum in src/command_type.h enum class Commands (starting at line 492) |
| Group GUI | src/group_gui.cpp/.h, src/vehiclelist.cpp |
| Vehicle↔Group | src/vehicle_base.h (Vehicle::group_id), SetTrainGroupID/UpdateTrainGroupID (group.h:130-131) |
| Orders/shared dispatch | src/order_base.h (OrderList, VehicleOrdersID), src/order_cmd.cpp, src/order_func.h, src/schdispatch.h/.cpp (scheduled dispatch, bound to order list) |
| Group statistics | GroupStatistics (group.h:60-66), GetGroupNumVehicle etc. (group.h:125-128) |
Current conclusion: CmdAddSharedVehicleGroup (adds shared-order vehicles of one vehicle to a group) and CmdCreateGroupFromList (create group from list) already exist, but there is no complete "auto-create/assign groups by shared orders" logic. The path for a new AutoGroupSharedOrders command (iterate all company primary vehicles → aggregate by OrderList → auto-create groups and assign) is clear.
4.4 Build Tooltip (Price Display at Mouse Cursor When Building Rails, etc.)
| Point of Interest | Location |
|---|---|
| Rail build GUI/logic | src/rail_gui.cpp (BuildRailToolbarWindow), src/rail_cmd.cpp, src/rail.h/.cpp; roads road_gui.cpp/road_cmd.cpp |
| Cost estimation | DoCommand in each *_cmd.cpp returns CommandCost; GUI can use DC_QUERY_COST mode for pricing |
| Tile under cursor | src/viewport_func.h:36 — GetTileBelowCursor(); _cursor.pos (screen coordinates); src/viewport.cpp:1056 |
| Existing text display mechanism | src/texteff.hpp — AddTextEffect(msg, x, y, duration, mode, ...) (world coordinate floating text), UpdateTextEffect; src/texteff.cpp |
| Existing build hint UX | Rail toolbar OnPlaceDrag shows selected area and cost accumulation (_thd tilehighlight, src/tilehighlight_func.h); status bar statusbar_gui.cpp can display tool cost |
| Per-frame refresh point | Each toolbar window OnMouseLoop / viewport.cpp HandleMouseEvents (lines 5422/5733) |
Current conclusion: No "price tooltip at mouse cursor." Can use AddTextEffect anchored to the tile under the cursor (follows the cursor tile), or self-draw a screen coordinate tooltip; cost can be queried with DC_QUERY_COST for a single tile + accumulated selection area.
4.5 AI Interface (NoAI Retained + Whole-Game-Aware AI)
| Point of Interest | Location |
|---|---|
| NoAI framework | src/ai/ — ai_core.cpp (AICore main loop), ai_instance.cpp (AIInstance/Squirrel VM), ai_scanner.cpp (scans ai/ directory), ai_gui.cpp (selection/configuration), ai_config.cpp |
| GameScript framework | src/game/ — game_core.cpp, game_instance.cpp, etc. (GS is "deity mode," higher privileges than AI) |
| Script API classes | src/script/api/script_*.hpp/.cpp (script_company, script_map, script_vehicle, script_industry, script_town, script_game, script_admin, etc., 60+ classes total) |
| API auto-registration | src/script/api/CMakeLists.txt — file(GLOB script_*.hpp) auto-generates ai_*.sq.hpp/gs_*.sq.hpp bindings; .cpp must be added to source list (starting at line 235) |
| Squirrel binding macros | src/script/squirrel_class.hpp — DefSQClass / DefSQStaticMethod; ai/ai_controller.sq.hpp is the AI controller binding |
| Instance scheduling | src/script/script_instance.cpp, src/script/script_suspend.hpp (suspend/resume), events script_event* |
| Company access control | src/script/api/script_object.hpp:318 — ScriptObject::GetCompany(); ScriptCompanyMode (IsDeity()) distinguishes GS deity mode; company API parameter validation ResolveCompanyID, EnforceCompanyModeValid |
| Script settings | src/table/settings/script_settings.ini (game.script.*); AI instance configuration ai_config.cpp |
Current conclusion: In the modern script API, some competitor information (e.g., GetBankBalance) is no longer restricted, but there is no aggregated API for whole-game awareness (no unified entry point for "enumerate all companies / global economy / global map statistics"), nor a toggle-controlled "global AI" access point. Adding a ScriptGlobal API class (GLOB auto-registered) + a game.script setting to "allow AI global awareness" is sufficient; GS is always available (deity mode), AI is controlled by the toggle — achieving "retain NoAI, add whole-game-aware AI, with access control."
5. Modification Risk Overview
| Feature | Main Files Changed | Risk |
|---|---|---|
| F1 Download multi-source/multi-thread | src/table/settings/network_settings.ini, src/settings_type.h, src/network/core/config.cpp, src/network/network_content.h/.cpp | Medium (network callback state machine needs care) |
| F2 Limit expansion | src/company_type.h, src/table/settings/network_settings.ini, src/network/core/network_game_info.cpp (optional uint16) | Low-Medium (company 252 low risk; clients >255 requires protocol change) |
| F3 Vehicle auto-grouping | src/group_cmd.h/.cpp, src/command_type.h, src/console_cmds.cpp, src/group_gui.cpp, src/lang/english.txt | Low |
| F4 Build tooltip | New src/construction_cost_tip.h/.cpp, src/rail_gui.cpp/road_gui.cpp hooks, src/lang/english.txt | Low-Medium |
| F5 Whole-game-aware AI | New src/script/api/script_global.hpp/.cpp, src/script/api/CMakeLists.txt, src/table/settings/script_settings.ini, example AI bin/ai/GlobalAI/ | Low (framework auto-registers) |
Note: All modifications are based on the local
jgrppbranch workspace, not compiled/verified (no usable build toolchain available on this machine); all patches can be reviewed viagit diffand thengit apply.