Added real timetable support
This commit is contained in:
@@ -26,13 +26,16 @@ RUN cmake .. \
|
||||
&& make -j$(nproc) install
|
||||
|
||||
# Runtime stage
|
||||
FROM debian:bookworm-slim@sha256:7b140f374b289a7c2befc338f42ebe6441b7ea838a042bbd5acbfca6ec875818
|
||||
# Must track the builder's Debian release: the builder (debian:13/trixie) links
|
||||
# against glibc 2.38+, so an older runtime (e.g. bookworm, glibc 2.36) cannot run
|
||||
# the resulting binary. Package names use the trixie t64 spelling.
|
||||
FROM debian:trixie-slim@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libcurl3-gnutls \
|
||||
libcurl3t64-gnutls \
|
||||
liblzma5 \
|
||||
liblzo2-2 \
|
||||
libpng16-16 \
|
||||
libpng16-16t64 \
|
||||
libzstd1 \
|
||||
zlib1g \
|
||||
ca-certificates \
|
||||
|
||||
@@ -25,13 +25,18 @@ This setup builds OpenTTD with the JGR Patch Pack (JGRPP) from source and runs i
|
||||
Save games are stored in `config/save/`.
|
||||
|
||||
## JGRPP Source
|
||||
The source code is cloned from the `jgrpp` branch of `https://github.com/JGRennison/OpenTTD-patches`.
|
||||
The source code is cloned from the `jgrpp` branch of `https://github.com/JGRennison/OpenTTD-patches`
|
||||
(currently at tag `jgrpp-0.71.1`) and carries local patches from `patches/` — see
|
||||
[patches/README.md](patches/README.md). After a fresh clone of the source, apply them with
|
||||
`git -C OpenTTD-patches am ../patches/*.patch` before building.
|
||||
|
||||
To update the server to a newer JGRPP version:
|
||||
1. Update the `OpenTTD-patches` directory:
|
||||
```bash
|
||||
cd OpenTTD-patches && git pull && cd ..
|
||||
```
|
||||
2. Rebuild the image:
|
||||
2. Reapply (rebase if needed) the local patches from `patches/`.
|
||||
3. Rebuild the image:
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
From 73f6770eb3e0ec5be8da9e74fa0874fb6ba2d36d Mon Sep 17 00:00:00 2001
|
||||
From: kovagoadi <kovagoadi@gmail.com>
|
||||
Date: Sun, 19 Jul 2026 00:33:23 +0200
|
||||
Subject: [PATCH] Add GameScript API timetable getters to ScriptOrder
|
||||
|
||||
Expose read-only timetable data to AI/GS scripts: per-order wait/travel
|
||||
times, timetabled/fixed flags, leave type and max speed, plus per-vehicle
|
||||
lateness, timetable start tick, current order time and total duration.
|
||||
|
||||
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||||
---
|
||||
src/script/api/script_order.cpp | 104 ++++++++++++++++++++++++++++
|
||||
src/script/api/script_order.hpp | 116 ++++++++++++++++++++++++++++++++
|
||||
2 files changed, 220 insertions(+)
|
||||
|
||||
diff --git a/src/script/api/script_order.cpp b/src/script/api/script_order.cpp
|
||||
index 865df623f9..ee18f7d588 100644
|
||||
--- a/src/script/api/script_order.cpp
|
||||
+++ b/src/script/api/script_order.cpp
|
||||
@@ -718,3 +718,107 @@ static void _DoCommandReturnSetOrderFlags(class ScriptInstance &instance)
|
||||
return ScriptMap::DistanceManhattan(origin_tile, dest_tile);
|
||||
}
|
||||
}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableWaitTime(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return -1;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return -1;
|
||||
+ return order->GetWaitTime();
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableTravelTime(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return -1;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return -1;
|
||||
+ return order->GetTravelTime();
|
||||
+}
|
||||
+
|
||||
+/* static */ bool ScriptOrder::IsWaitTimetabled(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return false;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return false;
|
||||
+ return order->IsWaitTimetabled();
|
||||
+}
|
||||
+
|
||||
+/* static */ bool ScriptOrder::IsTravelTimetabled(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return false;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return false;
|
||||
+ return order->IsTravelTimetabled();
|
||||
+}
|
||||
+
|
||||
+/* static */ bool ScriptOrder::IsWaitFixed(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return false;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return false;
|
||||
+ return order->IsWaitFixed();
|
||||
+}
|
||||
+
|
||||
+/* static */ bool ScriptOrder::IsTravelFixed(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return false;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return false;
|
||||
+ return order->IsTravelFixed();
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetLeaveType(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return -1;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return -1;
|
||||
+ return order->GetLeaveType();
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableMaxSpeed(VehicleID vehicle_id, OrderPosition order_position)
|
||||
+{
|
||||
+ if (!IsValidVehicleOrder(vehicle_id, order_position)) return -1;
|
||||
+
|
||||
+ const Order *order = ::ResolveOrder(vehicle_id, order_position);
|
||||
+ if (order == nullptr) return -1;
|
||||
+ return order->GetMaxSpeed();
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableLateness(VehicleID vehicle_id)
|
||||
+{
|
||||
+ if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return 0;
|
||||
+
|
||||
+ return ::Vehicle::Get(vehicle_id)->lateness_counter;
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableStartTick(VehicleID vehicle_id)
|
||||
+{
|
||||
+ if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return -1;
|
||||
+
|
||||
+ return ::Vehicle::Get(vehicle_id)->timetable_start.base();
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetCurrentOrderTime(VehicleID vehicle_id)
|
||||
+{
|
||||
+ if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return -1;
|
||||
+
|
||||
+ return ::Vehicle::Get(vehicle_id)->current_order_time;
|
||||
+}
|
||||
+
|
||||
+/* static */ SQInteger ScriptOrder::GetTimetableTotalDuration(VehicleID vehicle_id)
|
||||
+{
|
||||
+ if (!ScriptVehicle::IsPrimaryVehicle(vehicle_id)) return -1;
|
||||
+
|
||||
+ const Vehicle *v = ::Vehicle::Get(vehicle_id);
|
||||
+ if (v->orders == nullptr) return -1;
|
||||
+ Ticks duration = v->orders->GetTimetableTotalDuration();
|
||||
+ if (duration == INVALID_TICKS) return -1;
|
||||
+ return duration;
|
||||
+}
|
||||
diff --git a/src/script/api/script_order.hpp b/src/script/api/script_order.hpp
|
||||
index 81dc06cd7d..6c96b91b3a 100644
|
||||
--- a/src/script/api/script_order.hpp
|
||||
+++ b/src/script/api/script_order.hpp
|
||||
@@ -604,6 +604,122 @@ public:
|
||||
* @see ScriptEngine::GetMaximumOrderDistance and ScriptVehicle::GetMaximumOrderDistance
|
||||
*/
|
||||
static SQInteger GetOrderDistance(ScriptVehicle::VehicleType vehicle_type, TileIndex origin_tile, TileIndex dest_tile);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the timetabled wait time of the given order for the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the timetable wait time for.
|
||||
+ * @param order_position The order to get the timetable wait time for.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return The wait time of the order in ticks, or -1 when the order is invalid.
|
||||
+ * @note The raw stored wait time is returned even if the wait time is not
|
||||
+ * timetabled; use IsWaitTimetabled to check whether it is explicitly set.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableWaitTime(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the timetabled travel time of the given order for the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the timetable travel time for.
|
||||
+ * @param order_position The order to get the timetable travel time for.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return The travel time of the order in ticks, or -1 when the order is invalid.
|
||||
+ * @note The raw stored travel time is returned even if the travel time is not
|
||||
+ * timetabled; use IsTravelTimetabled to check whether it is explicitly set.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableTravelTime(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Checks whether the wait time of the given order is timetabled (explicitly set).
|
||||
+ * @param vehicle_id The vehicle to check the order for.
|
||||
+ * @param order_position The order to check.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return True if and only if the wait time is timetabled.
|
||||
+ */
|
||||
+ static bool IsWaitTimetabled(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Checks whether the travel time of the given order is timetabled (explicitly set).
|
||||
+ * @param vehicle_id The vehicle to check the order for.
|
||||
+ * @param order_position The order to check.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return True if and only if the travel time is timetabled.
|
||||
+ */
|
||||
+ static bool IsTravelTimetabled(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Checks whether the wait time of the given order is fixed (locked against autofill).
|
||||
+ * @param vehicle_id The vehicle to check the order for.
|
||||
+ * @param order_position The order to check.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return True if and only if the wait time is fixed.
|
||||
+ */
|
||||
+ static bool IsWaitFixed(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Checks whether the travel time of the given order is fixed (locked against autofill).
|
||||
+ * @param vehicle_id The vehicle to check the order for.
|
||||
+ * @param order_position The order to check.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return True if and only if the travel time is fixed.
|
||||
+ */
|
||||
+ static bool IsTravelFixed(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the leave type of the given order for the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the leave type for.
|
||||
+ * @param order_position The order to get the leave type for.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return The leave type of the order (0 = leave when timetabled, 1 = leave as
|
||||
+ * soon as possible, 2 = leave early if any cargo fully loaded, 3 = leave early
|
||||
+ * if all cargo fully loaded), or -1 when the order is invalid.
|
||||
+ */
|
||||
+ static SQInteger GetLeaveType(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the timetabled maximum speed of the given order for the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the timetable max speed for.
|
||||
+ * @param order_position The order to get the timetable max speed for.
|
||||
+ * @pre IsValidVehicleOrder(vehicle_id, order_position).
|
||||
+ * @return The maximum speed of the order (65535 when no speed cap is set),
|
||||
+ * or -1 when the order is invalid.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableMaxSpeed(VehicleID vehicle_id, OrderPosition order_position);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the timetable lateness of the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the lateness for.
|
||||
+ * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id).
|
||||
+ * @return How many ticks the vehicle is late; negative values mean the vehicle
|
||||
+ * is running early. Returns 0 when the vehicle is invalid, which is
|
||||
+ * indistinguishable from an on-time vehicle; check the vehicle validity first.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableLateness(VehicleID vehicle_id);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the state tick at which the timetable of the given vehicle starts.
|
||||
+ * @param vehicle_id The vehicle to get the timetable start tick for.
|
||||
+ * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id).
|
||||
+ * @return The absolute state tick the timetable starts at (0 when the
|
||||
+ * timetable has not been started), or -1 when the vehicle is invalid.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableStartTick(VehicleID vehicle_id);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the number of ticks the given vehicle has spent on its current order.
|
||||
+ * @param vehicle_id The vehicle to get the current order time for.
|
||||
+ * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id).
|
||||
+ * @return The number of ticks spent on the current order, or -1 when the
|
||||
+ * vehicle is invalid.
|
||||
+ */
|
||||
+ static SQInteger GetCurrentOrderTime(VehicleID vehicle_id);
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the total duration of the timetable of the given vehicle.
|
||||
+ * @param vehicle_id The vehicle to get the timetable duration for.
|
||||
+ * @pre ScriptVehicle::IsPrimaryVehicle(vehicle_id).
|
||||
+ * @return The total timetable duration in ticks, or -1 when the vehicle is
|
||||
+ * invalid, has no orders, or the timetable is not complete.
|
||||
+ */
|
||||
+ static SQInteger GetTimetableTotalDuration(VehicleID vehicle_id);
|
||||
};
|
||||
DECLARE_ENUM_AS_BIT_SET(ScriptOrder::ScriptOrderFlags)
|
||||
|
||||
--
|
||||
2.54.0
|
||||
|
||||
28
docker/patches/README.md
Normal file
28
docker/patches/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Local JGRPP patches
|
||||
|
||||
The `docker/OpenTTD-patches/` directory is an **untracked** clone of
|
||||
[JGRennison/OpenTTD-patches](https://github.com/JGRennison/OpenTTD-patches) checked out at tag
|
||||
`jgrpp-0.71.1`. The patches in this directory are the local modifications this project needs on
|
||||
top of that tag; they are the durable source of truth (the clone itself is not committed).
|
||||
|
||||
Current patches:
|
||||
|
||||
- `0001-Add-GameScript-API-timetable-getters-to-ScriptOrder.patch` — adds read-only timetable
|
||||
getters (`GetTimetableWaitTime`, `GetTimetableTravelTime`, `IsWaitTimetabled`,
|
||||
`IsTravelTimetabled`, `IsWaitFixed`, `IsTravelFixed`, `GetLeaveType`, `GetTimetableMaxSpeed`,
|
||||
`GetTimetableLateness`, `GetTimetableStartTick`, `GetCurrentOrderTime`,
|
||||
`GetTimetableTotalDuration`) to the `GSOrder` GameScript class. Required by the AdminBridge
|
||||
GameScript's `get_timetable` command and the Python client's
|
||||
`OpenTTDAdminClient.get_timetable()`.
|
||||
|
||||
## Applying after a fresh clone
|
||||
|
||||
```bash
|
||||
git clone --branch jgrpp-0.71.1 https://github.com/JGRennison/OpenTTD-patches docker/OpenTTD-patches
|
||||
git -C docker/OpenTTD-patches am ../patches/*.patch
|
||||
```
|
||||
|
||||
Then build the image as usual (`docker-compose up -d --build` from `docker/`).
|
||||
|
||||
If the clone is updated past `jgrpp-0.71.1`, `git am` may conflict — the patches were generated
|
||||
against that tag and need rebasing in that case.
|
||||
Reference in New Issue
Block a user