# HG changeset patch # User Loïc Guilloux # Date 2024-01-01 00:07:47 # Node ID 799fec80d8df46991e30bd9315ea858cb1e07a9f # Parent 6d6dc72d078c47c5032be3a41171a1c6503ca03d Add: [Script] Optional filter parameter to ScriptVehicleList constructor (#11663) diff --git a/regression/regression/main.nut b/regression/regression/main.nut --- a/regression/regression/main.nut +++ b/regression/regression/main.nut @@ -1814,10 +1814,17 @@ function Regression::Vehicle() print(" GetLastErrorString(): " + AIError.GetLastErrorString()); local list = AIVehicleList(); + local in_depot = AIVehicleList(AIVehicle.IsInDepot); + local IsType = function(vehicle_id, type) { + return AIVehicle.GetVehicleType(vehicle_id) == type; + } + local rv_list = AIVehicleList(IsType, AIVehicle.VT_ROAD); print(""); print("--VehicleList--"); print(" Count(): " + list.Count()); + print(" InDepot Count(): " + in_depot.Count()); + print(" RoadVehicle Count(): " + rv_list.Count()); list.Valuate(AIVehicle.GetLocation); print(" Location ListDump:"); for (local i = list.Begin(); !list.IsEnd(); i = list.Next()) { diff --git a/regression/regression/result.txt b/regression/regression/result.txt --- a/regression/regression/result.txt +++ b/regression/regression/result.txt @@ -9391,6 +9391,8 @@ ERROR: IsEnd() is invalid as Begin() is --VehicleList-- Count(): 5 + InDepot Count(): 4 + RoadVehicle Count(): 2 Location ListDump: 13 => 33417 12 => 33417 diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -24,6 +24,9 @@ * API removals: * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * + * Other changes: + * \li AIVehicleList accepts an optional filter function + * * \b 13.0 * * API additions: diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -84,6 +84,9 @@ * API removals: * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * + * Other changes: + * \li GSVehicleList accepts an optional filter function + * * \b 13.0 * * API additions: diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -18,12 +18,78 @@ #include "../../safeguards.h" -ScriptVehicleList::ScriptVehicleList() +ScriptVehicleList::ScriptVehicleList(HSQUIRRELVM vm) { EnforceDeityOrCompanyModeValid_Void(); + + int nparam = sq_gettop(vm) - 1; + if (nparam >= 1) { + /* Make sure the filter function is really a function, and not any + * other type. It's parameter 2 for us, but for the user it's the + * first parameter they give. */ + SQObjectType valuator_type = sq_gettype(vm, 2); + if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) { + throw sq_throwerror(vm, "parameter 1 has an invalid type (expected function)"); + } + + /* Push the function to call */ + sq_push(vm, 2); + } + + /* Don't allow docommand from a Valuator, as we can't resume in + * mid C++-code. */ + bool backup_allow = ScriptObject::GetAllowDoCommand(); + ScriptObject::SetAllowDoCommand(false); + for (const Vehicle *v : Vehicle::Iterate()) { - if ((v->owner == ScriptObject::GetCompany() || ScriptCompanyMode::IsDeity()) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon()))) this->AddItem(v->index); + if (v->owner != ScriptObject::GetCompany() && !ScriptCompanyMode::IsDeity()) continue; + if (!v->IsPrimaryVehicle() && !(v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())) continue; + + if (nparam < 1) { + /* No filter, just add the item. */ + this->AddItem(v->index); + continue; + } + + /* Push the root table as instance object, this is what squirrel does for meta-functions. */ + sq_pushroottable(vm); + /* Push all arguments for the valuator function. */ + sq_pushinteger(vm, v->index); + for (int i = 0; i < nparam - 1; i++) { + sq_push(vm, i + 3); + } + + /* Call the function. Squirrel pops all parameters and pushes the return value. */ + if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) { + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "failed to run filter"); + } + + /* Retrieve the return value */ + switch (sq_gettype(vm, -1)) { + case OT_BOOL: { + SQBool add; + sq_getbool(vm, -1, &add); + if (add) this->AddItem(v->index); + break; + } + + default: { + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "return value of filter is not valid (not bool)"); + } + } + + /* Pop the return value. */ + sq_poptop(vm); } + + if (nparam >= 1) { + /* Pop the filter function */ + sq_poptop(vm); + } + + ScriptObject::SetAllowDoCommand(backup_allow); } ScriptVehicleList_Station::ScriptVehicleList_Station(StationID station_id) diff --git a/src/script/api/script_vehiclelist.hpp b/src/script/api/script_vehiclelist.hpp --- a/src/script/api/script_vehiclelist.hpp +++ b/src/script/api/script_vehiclelist.hpp @@ -20,7 +20,32 @@ */ class ScriptVehicleList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptVehicleList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * ScriptVehicleList(ScriptVehicle.IsInDepot); + * function IsType(vehicle_id, type) + * { + * return ScriptVehicle.GetVehicleType(vehicle_id) == type; + * } + * ScriptVehicleList(IsType, ScriptVehicle.VT_ROAD); + */ + ScriptVehicleList(void *filter_function, int params, ...); +#else + /** + * The constructor wrapper from Squirrel. + */ + ScriptVehicleList(HSQUIRRELVM vm); +#endif }; /**