diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp index b8635c3522..4e03ae3f63 100644 --- a/src/ai/ai_gui.cpp +++ b/src/ai/ai_gui.cpp @@ -103,6 +103,11 @@ struct AIListWindow : public Window { sprintf(buf, "%d", selected_info->GetVersion()); DoDrawStringTruncated(buf, x + 5, y, TC_BLACK, this->width - x - 8); y += 13; + if (selected_info->GetURL() != NULL) { + SetDParamStr(0, selected_info->GetURL()); + DrawString(4, y, STR_AI_URL, TC_BLACK); + y += 13; + } SetDParamStr(0, selected_info->GetDescription()); DrawStringMultiLine(4, y, STR_JUST_RAW_STRING, this->width - 8, this->widget[AIL_WIDGET_INFO_BG].bottom - y); } @@ -575,6 +580,8 @@ struct AIDebugWindow : public Window { static CompanyID ai_debug_company; int redraw_timer; + int last_vscroll_pos; + bool autoscroll; AIDebugWindow(const WindowDesc *desc, WindowNumber number) : Window(desc, number) { @@ -587,6 +594,8 @@ struct AIDebugWindow : public Window { this->vscroll.cap = 14; this->vscroll.pos = 0; this->resize.step_height = 12; + this->last_vscroll_pos = 0; + this->autoscroll = true; if (ai_debug_company != INVALID_COMPANY) this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); @@ -664,13 +673,35 @@ struct AIDebugWindow : public Window { AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer(); _current_company = old_company; - SetVScrollCount(this, (log == NULL) ? 0 : log->used); - this->InvalidateWidget(AID_WIDGET_SCROLLBAR); + int scroll_count = (log == NULL) ? 0 : log->used; + if (this->vscroll.count != scroll_count) { + SetVScrollCount(this, scroll_count); + + /* We need a repaint */ + this->InvalidateWidget(AID_WIDGET_SCROLLBAR); + } + if (log == NULL) return; + /* Detect when the user scrolls the window. Enable autoscroll when the + * bottom-most line becomes visible. */ + if (this->last_vscroll_pos != this->vscroll.pos) { + this->autoscroll = this->vscroll.pos >= log->used - this->vscroll.cap; + } + if (this->autoscroll) { + int scroll_pos = max(0, log->used - this->vscroll.cap); + if (scroll_pos != this->vscroll.pos) { + this->vscroll.pos = scroll_pos; + + /* We need a repaint */ + this->InvalidateWidget(AID_WIDGET_SCROLLBAR); + } + } + last_vscroll_pos = this->vscroll.pos; + int y = 6; - for (int i = this->vscroll.pos; i < (this->vscroll.cap + this->vscroll.pos); i++) { - uint pos = (log->count + log->pos - i) % log->count; + for (int i = this->vscroll.pos; i < (this->vscroll.cap + this->vscroll.pos) && i < log->used; i++) { + uint pos = (i + log->pos + 1 - log->used + log->count) % log->count; if (log->lines[pos] == NULL) break; TextColour colour; @@ -693,6 +724,8 @@ struct AIDebugWindow : public Window { this->RaiseWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); ai_debug_company = show_ai; this->LowerWidget(ai_debug_company + AID_WIDGET_COMPANY_BUTTON_START); + this->autoscroll = true; + this->last_vscroll_pos = this->vscroll.pos; this->SetDirty(); } @@ -726,6 +759,7 @@ struct AIDebugWindow : public Window { virtual void OnResize(Point new_size, Point delta) { this->vscroll.cap += delta.y / (int)this->resize.step_height; + SetVScrollCount(this, this->vscroll.count); // vscroll.pos should be in a valid range } }; diff --git a/src/ai/ai_info.cpp b/src/ai/ai_info.cpp index 4d9cfa9a25..63ba7f54b6 100644 --- a/src/ai/ai_info.cpp +++ b/src/ai/ai_info.cpp @@ -36,6 +36,7 @@ AIFileInfo::~AIFileInfo() free((void *)this->description); free((void *)this->date); free((void *)this->instance_name); + free((void *)this->url); free(this->main_script); free(this->SQ_instance); } @@ -98,6 +99,11 @@ bool AIFileInfo::CheckMethod(const char *name) const if (!info->engine->CallIntegerMethod(*info->SQ_instance, "GetVersion", &info->version)) return SQ_ERROR; if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "CreateInstance", &info->instance_name)) return SQ_ERROR; + /* The GetURL function is optional. */ + if (info->engine->MethodExists(*info->SQ_instance, "GetURL")) { + if (!info->engine->CallStringMethodStrdup(*info->SQ_instance, "GetURL", &info->url)) return SQ_ERROR; + } + return 0; } @@ -125,6 +131,12 @@ bool AIFileInfo::CheckMethod(const char *name) const } else { info->min_loadable_version = info->GetVersion(); } + /* When there is an UseAsRandomAI function, call it. */ + if (info->engine->MethodExists(*info->SQ_instance, "UseAsRandomAI")) { + if (!info->engine->CallBoolMethod(*info->SQ_instance, "UseAsRandomAI", &info->use_as_random)) return SQ_ERROR; + } else { + info->use_as_random = true; + } /* Remove the link to the real instance, else it might get deleted by RegisterAI() */ sq_setinstanceup(vm, 2, NULL); diff --git a/src/ai/ai_info.hpp b/src/ai/ai_info.hpp index ef8505e2d8..48d6db33fe 100644 --- a/src/ai/ai_info.hpp +++ b/src/ai/ai_info.hpp @@ -41,7 +41,7 @@ public: friend class AIInfo; friend class AILibrary; - AIFileInfo() : SQ_instance(NULL), main_script(NULL), author(NULL), name(NULL), short_name(NULL), description(NULL), date(NULL), instance_name(NULL) {}; + AIFileInfo() : SQ_instance(NULL), main_script(NULL), author(NULL), name(NULL), short_name(NULL), description(NULL), date(NULL), instance_name(NULL), url(NULL) {}; ~AIFileInfo(); /** @@ -84,6 +84,11 @@ public: */ const char *GetInstanceName() const { return this->instance_name; } + /** + * Get the website for this script. + */ + const char *GetURL() const { return this->url; } + /** * Get the filename of the main.nut script. */ @@ -111,6 +116,7 @@ private: const char *date; const char *instance_name; int version; + const char *url; }; class AIInfo : public AIFileInfo { @@ -155,9 +161,15 @@ public: */ int GetSettingDefaultValue(const char *name) const; + /** + * Use this AI as a random AI. + */ + bool UseAsRandomAI() const { return this->use_as_random; } + private: AIConfigItemList config_list; int min_loadable_version; + bool use_as_random; }; class AILibrary : public AIFileInfo { diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp index 03f6b67ba6..dcd68410b7 100644 --- a/src/ai/ai_instance.cpp +++ b/src/ai/ai_instance.cpp @@ -19,6 +19,7 @@ #define DEFINE_SCRIPT_FILES #include "ai_info.hpp" +#include "ai_config.hpp" #include "ai_storage.hpp" #include "ai_instance.hpp" #include "ai_gui.hpp" @@ -258,6 +259,12 @@ void AIInstance::Died() if (strcmp(GetCompany(_current_company)->ai_info->GetMainScript(), "%_dummy") != 0) { ShowErrorMessage(INVALID_STRING_ID, STR_AI_PLEASE_REPORT_CRASH, 0, 0); } + + const AIInfo *info = AIConfig::GetConfig(_current_company)->GetInfo(); + if (info->GetURL() != NULL) { + AILog::Info("Please report the error to the following URL:"); + AILog::Info(info->GetURL()); + } } void AIInstance::GameLoop() diff --git a/src/ai/ai_scanner.cpp b/src/ai/ai_scanner.cpp index 716df088dc..c4bde89b32 100644 --- a/src/ai/ai_scanner.cpp +++ b/src/ai/ai_scanner.cpp @@ -345,20 +345,31 @@ void AIScanner::RegisterAI(AIInfo *info) AIInfo *AIScanner::SelectRandomAI() { - if (this->info_single_list.size() == 0) { + uint num_random_ais = 0; + for (AIInfoList::iterator it = this->info_single_list.begin(); it != this->info_single_list.end(); it++) { + if (it->second->UseAsRandomAI()) num_random_ais++; + } + + if (num_random_ais == 0) { DEBUG(ai, 0, "No suitable AI found, loading 'dummy' AI."); return this->info_dummy; } /* Find a random AI */ uint pos; - if (_networking) pos = InteractiveRandomRange((uint16)this->info_single_list.size()); - else pos = RandomRange((uint16)this->info_single_list.size()); + if (_networking) { + pos = InteractiveRandomRange(num_random_ais); + } else { + pos = RandomRange(num_random_ais); + } /* Find the Nth item from the array */ AIInfoList::iterator it = this->info_single_list.begin(); - for (; pos > 0; pos--) it++; - AIInfoList::iterator first_it = it; + while (!it->second->UseAsRandomAI()) it++; + for (; pos > 0; pos--) { + it++; + while (!it->second->UseAsRandomAI()) it++; + } return (*it).second; } diff --git a/src/ai/api/ai_log.cpp b/src/ai/api/ai_log.cpp index 6dc9cb365b..8e37147734 100644 --- a/src/ai/api/ai_log.cpp +++ b/src/ai/api/ai_log.cpp @@ -32,7 +32,7 @@ log->lines = CallocT(80); log->type = CallocT(80); log->count = 80; - log->pos = log->count; + log->pos = log->count - 1; log->used = 0; } LogData *log = (LogData *)AIObject::GetLogPointer(); diff --git a/src/ai/api/ai_order.cpp b/src/ai/api/ai_order.cpp index 2e901ea0a8..5d1165eba2 100644 --- a/src/ai/api/ai_order.cpp +++ b/src/ai/api/ai_order.cpp @@ -23,7 +23,10 @@ static OrderType GetOrderTypeByTile(TileIndex t) switch (::GetTileType(t)) { default: break; - case MP_STATION: return OT_GOTO_STATION; break; + case MP_STATION: + if (IsHangar(t)) return OT_GOTO_DEPOT; + return OT_GOTO_STATION; + break; case MP_WATER: if (::IsShipDepot(t)) return OT_GOTO_DEPOT; break; case MP_ROAD: if (::GetRoadTileType(t) == ROAD_TILE_DEPOT) return OT_GOTO_DEPOT; break; case MP_RAILWAY: @@ -74,7 +77,10 @@ static OrderType GetOrderTypeByTile(TileIndex t) (((order_flags & AIOF_NO_UNLOAD) == 0) || ((order_flags & AIOF_NO_LOAD) == 0)) && (((order_flags & AIOF_FULL_LOAD_ANY) == 0) || ((order_flags & AIOF_NO_LOAD) == 0)); - case OT_GOTO_DEPOT: return (order_flags & ~(AIOF_NON_STOP_FLAGS | AIOF_SERVICE_IF_NEEDED)) == 0; + case OT_GOTO_DEPOT: + return ((order_flags & ~(AIOF_NON_STOP_FLAGS | AIOF_DEPOT_FLAGS)) == 0) && + (((order_flags & AIOF_SERVICE_IF_NEEDED) == 0) || ((order_flags & AIOF_STOP_IN_DEPOT) == 0)); + case OT_GOTO_WAYPOINT: return (order_flags & ~(AIOF_NON_STOP_FLAGS)) == 0; default: return false; } @@ -118,11 +124,36 @@ static OrderType GetOrderTypeByTile(TileIndex t) } switch (order->GetType()) { - case OT_GOTO_DEPOT: + case OT_GOTO_DEPOT: { if (v->type != VEH_AIRCRAFT) return ::GetDepot(order->GetDestination())->xy; - /* FALL THROUGH: aircraft's hangars are referenced by StationID, not DepotID */ + /* Aircraft's hangars are referenced by StationID, not DepotID */ + const Station *st = ::GetStation(order->GetDestination()); + const AirportFTAClass *airport = st->Airport(); + if (airport == NULL || airport->nof_depots == 0) return INVALID_TILE; + return st->airport_tile + ::ToTileIndexDiff(st->Airport()->airport_depots[0]); + } - case OT_GOTO_STATION: return ::GetStation(order->GetDestination())->xy; + case OT_GOTO_STATION: { + const Station *st = ::GetStation(order->GetDestination()); + if (st->train_tile != INVALID_TILE) { + for (uint i = 0; i < st->trainst_w; i++) { + TileIndex t = st->train_tile + TileDiffXY(i, 0); + if (st->TileBelongsToRailStation(t)) return t; + } + } else if (st->dock_tile != INVALID_TILE) { + return st->dock_tile; + } else if (st->bus_stops != NULL) { + return st->bus_stops->xy; + } else if (st->truck_stops != NULL) { + return st->truck_stops->xy; + } else if (st->airport_tile != INVALID_TILE) { + const AirportFTAClass *fta = st->Airport(); + BEGIN_TILE_LOOP(tile, fta->size_x, fta->size_y, st->airport_tile) { + if (!::IsHangar(tile)) return tile; + } END_TILE_LOOP(tile, fta->size_x, fta->size_y, st->airport_tile) + } + return INVALID_TILE; + } case OT_GOTO_WAYPOINT: return ::GetWaypoint(order->GetDestination())->xy; default: return INVALID_TILE; } @@ -145,6 +176,7 @@ static OrderType GetOrderTypeByTile(TileIndex t) switch (order->GetType()) { case OT_GOTO_DEPOT: if (order->GetDepotOrderType() & ODTFB_SERVICE) order_flags |= AIOF_SERVICE_IF_NEEDED; + if (order->GetDepotActionType() & ODATFB_HALT) order_flags |= AIOF_STOP_IN_DEPOT; break; case OT_GOTO_STATION: @@ -260,9 +292,20 @@ static OrderType GetOrderTypeByTile(TileIndex t) Order order; switch (::GetOrderTypeByTile(destination)) { - case OT_GOTO_DEPOT: - order.MakeGoToDepot(::GetDepotByTile(destination)->index, (OrderDepotTypeFlags)(ODTFB_PART_OF_ORDERS | ((order_flags & AIOF_SERVICE_IF_NEEDED) ? ODTFB_SERVICE : 0))); + case OT_GOTO_DEPOT: { + OrderDepotTypeFlags odtf = (OrderDepotTypeFlags)(ODTFB_PART_OF_ORDERS | ((order_flags & AIOF_SERVICE_IF_NEEDED) ? ODTFB_SERVICE : 0)); + OrderDepotActionFlags odaf = (OrderDepotActionFlags)(ODATF_SERVICE_ONLY | ((order_flags & AIOF_STOP_IN_DEPOT) ? ODATFB_HALT : 0)); + /* Check explicitly if the order is to a station (for aircraft) or + * to a depot (other vehicle types). */ + if (::GetVehicle(vehicle_id)->type == VEH_AIRCRAFT) { + if (!::IsTileType(destination, MP_STATION)) return false; + order.MakeGoToDepot(::GetStationIndex(destination), odtf, odaf); + } else { + if (::IsTileType(destination, MP_STATION)) return false; + order.MakeGoToDepot(::GetDepotByTile(destination)->index, odtf, odaf); + } break; + } case OT_GOTO_STATION: order.MakeGoToStation(::GetStationIndex(destination)); @@ -356,8 +399,11 @@ static void _DoCommandReturnSetOrderFlags(class AIInstance *instance) switch (order->GetType()) { case OT_GOTO_DEPOT: - if ((current & AIOF_SERVICE_IF_NEEDED) != (order_flags & AIOF_SERVICE_IF_NEEDED)) { - return AIObject::DoCommand(0, vehicle_id | (order_position << 16), MOF_DEPOT_ACTION, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnSetOrderFlags); + if ((current & AIOF_DEPOT_FLAGS) != (order_flags & AIOF_DEPOT_FLAGS)) { + uint data = DA_ALWAYS_GO; + if (order_flags & AIOF_SERVICE_IF_NEEDED) data = DA_SERVICE; + if (order_flags & AIOF_STOP_IN_DEPOT) data = DA_STOP; + return AIObject::DoCommand(0, vehicle_id | (order_position << 16), (data << 4) | MOF_DEPOT_ACTION, CMD_MODIFY_ORDER, NULL, &_DoCommandReturnSetOrderFlags); } break; diff --git a/src/ai/api/ai_order.hpp b/src/ai/api/ai_order.hpp index dd26c819c9..8a08b505a0 100644 --- a/src/ai/api/ai_order.hpp +++ b/src/ai/api/ai_order.hpp @@ -57,6 +57,8 @@ public: /** Service the vehicle when needed, otherwise skip this order; only for depots. */ AIOF_SERVICE_IF_NEEDED = 1 << 2, + /** Stop in the depot instead of only go there for servicing; only for depots. */ + AIOF_STOP_IN_DEPOT = 1 << 3, /** All flags related to non-stop settings. */ AIOF_NON_STOP_FLAGS = AIOF_NON_STOP_INTERMEDIATE | AIOF_NON_STOP_DESTINATION, @@ -64,6 +66,8 @@ public: AIOF_UNLOAD_FLAGS = AIOF_TRANSFER | AIOF_UNLOAD | AIOF_NO_UNLOAD, /** All flags related to loading. */ AIOF_LOAD_FLAGS = AIOF_FULL_LOAD | AIOF_FULL_LOAD_ANY | AIOF_NO_LOAD, + /** All flags related to depots. */ + AIOF_DEPOT_FLAGS = AIOF_SERVICE_IF_NEEDED | AIOF_STOP_IN_DEPOT, /** For marking invalid order flags */ AIOF_INVALID = 0xFFFF, diff --git a/src/ai/api/ai_order.hpp.sq b/src/ai/api/ai_order.hpp.sq index 3164c10988..d2ac2ae8a4 100644 --- a/src/ai/api/ai_order.hpp.sq +++ b/src/ai/api/ai_order.hpp.sq @@ -42,9 +42,11 @@ void SQAIOrder_Register(Squirrel *engine) { SQAIOrder.DefSQConst(engine, AIOrder::AIOF_FULL_LOAD_ANY, "AIOF_FULL_LOAD_ANY"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NO_LOAD, "AIOF_NO_LOAD"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_SERVICE_IF_NEEDED, "AIOF_SERVICE_IF_NEEDED"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_STOP_IN_DEPOT, "AIOF_STOP_IN_DEPOT"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_NON_STOP_FLAGS, "AIOF_NON_STOP_FLAGS"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_UNLOAD_FLAGS, "AIOF_UNLOAD_FLAGS"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_LOAD_FLAGS, "AIOF_LOAD_FLAGS"); + SQAIOrder.DefSQConst(engine, AIOrder::AIOF_DEPOT_FLAGS, "AIOF_DEPOT_FLAGS"); SQAIOrder.DefSQConst(engine, AIOrder::AIOF_INVALID, "AIOF_INVALID"); SQAIOrder.DefSQConst(engine, AIOrder::OC_LOAD_PERCENTAGE, "OC_LOAD_PERCENTAGE"); SQAIOrder.DefSQConst(engine, AIOrder::OC_RELIABILITY, "OC_RELIABILITY"); diff --git a/src/lang/english.txt b/src/lang/english.txt index 434b4a4f27..39d37d186d 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -3585,6 +3585,7 @@ STR_AI_RANDOM_AI :Random AI STR_AI_SETTINGS_CAPTION :{WHITE}AI Parameters STR_AI_AUTHOR :Author: STR_AI_VERSION :Version: +STR_AI_URL :URL: {RAW_STRING} STR_AI_PLEASE_REPORT_CRASH :{WHITE}One of the running AIs crashed. Please report this to the AI author with a screenshot of the AI Debug Window. ######## diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index b980d1711d..d54baab45d 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -498,7 +498,8 @@ CommandCost CmdInsertOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 if (new_order.GetNonStopType() != ONSF_STOP_EVERYWHERE && v->type != VEH_TRAIN && v->type != VEH_ROAD) return CMD_ERROR; if (new_order.GetDepotOrderType() & ~(ODTFB_PART_OF_ORDERS | ((new_order.GetDepotOrderType() & ODTFB_PART_OF_ORDERS) != 0 ? ODTFB_SERVICE : 0))) return CMD_ERROR; - if (new_order.GetDepotActionType() & ~ODATFB_NEAREST_DEPOT) return CMD_ERROR; + if (new_order.GetDepotActionType() & ~(ODATFB_HALT | ODATFB_NEAREST_DEPOT)) return CMD_ERROR; + if ((new_order.GetDepotOrderType() & ODTFB_SERVICE) && (new_order.GetDepotActionType() & ODATFB_HALT)) return CMD_ERROR; break; } @@ -862,7 +863,7 @@ CommandCost CmdModifyOrder(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3 break; case OT_CONDITIONAL: - if (mof != MOF_COND_VARIABLE && mof != MOF_COND_COMPARATOR && mof != MOF_COND_VALUE) return CMD_ERROR; + if (mof != MOF_COND_VARIABLE && mof != MOF_COND_COMPARATOR && mof != MOF_COND_VALUE && mof != MOF_COND_DESTINATION) return CMD_ERROR; break; default: diff --git a/src/script/squirrel.cpp b/src/script/squirrel.cpp index a62c834188..f31b813da3 100644 --- a/src/script/squirrel.cpp +++ b/src/script/squirrel.cpp @@ -234,6 +234,15 @@ bool Squirrel::CallIntegerMethod(HSQOBJECT instance, const char *method_name, in return true; } +bool Squirrel::CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend) +{ + HSQOBJECT ret; + if (!this->CallMethod(instance, method_name, &ret, suspend)) return false; + if (ret._type != OT_BOOL) return false; + *res = ObjectToBool(&ret); + return true; +} + /* static */ bool Squirrel::CreateClassInstanceVM(HSQUIRRELVM vm, const char *class_name, void *real_instance, HSQOBJECT *instance, SQRELEASEHOOK release_hook) { int oldtop = sq_gettop(vm); diff --git a/src/script/squirrel.hpp b/src/script/squirrel.hpp index 31b36f9683..ee597f87ad 100644 --- a/src/script/squirrel.hpp +++ b/src/script/squirrel.hpp @@ -116,6 +116,7 @@ public: bool CallMethod(HSQOBJECT instance, const char *method_name, int suspend = -1) { return this->CallMethod(instance, method_name, NULL, suspend); } bool CallStringMethodStrdup(HSQOBJECT instance, const char *method_name, const char **res, int suspend = -1); bool CallIntegerMethod(HSQOBJECT instance, const char *method_name, int *res, int suspend = -1); + bool CallBoolMethod(HSQOBJECT instance, const char *method_name, bool *res, int suspend = -1); /** * Check if a method exists in an instance. @@ -161,6 +162,11 @@ public: */ static int ObjectToInteger(HSQOBJECT *ptr) { return sq_objtointeger(ptr); } + /** + * Convert a Squirrel-object to a bool. + */ + static bool ObjectToBool(HSQOBJECT *ptr) { return sq_objtobool(ptr) == 1; } + /** * Sets a pointer in the VM that is reachable from where ever you are in SQ. * Useful to keep track of the main instance.