diff --git a/src/ai/ai.c b/src/ai/ai.c deleted file mode 100644 --- a/src/ai/ai.c +++ /dev/null @@ -1,247 +0,0 @@ -/* $Id$ */ - -#include "../stdafx.h" -#include "../openttd.h" -#include "../variables.h" -#include "../command.h" -#include "../network/network.h" -#include "ai.h" -#include "default/default.h" - -/** - * Dequeues commands put in the queue via AI_PutCommandInQueue. - */ -static void AI_DequeueCommands(PlayerID player) -{ - AICommand *com, *entry_com; - - entry_com = _ai_player[player].queue; - - /* It happens that DoCommandP issues a new DoCommandAI which adds a new command - * to this very same queue (don't argue about this, if it currently doesn't - * happen I can tell you it will happen with AIScript -- TrueLight). If we - * do not make the queue NULL, that commands will be dequeued immediatly. - * Therefor we safe the entry-point to entry_com, and make the queue NULL, so - * the new queue can be safely built up. */ - _ai_player[player].queue = NULL; - _ai_player[player].queue_tail = NULL; - - /* Dequeue all commands */ - while ((com = entry_com) != NULL) { - _current_player = player; - - _cmd_text = com->text; - DoCommandP(com->tile, com->p1, com->p2, com->callback, com->procc); - - /* Free item */ - entry_com = com->next; - free(com->text); - free(com); - } -} - -/** - * Needed for SP; we need to delay DoCommand with 1 tick, because else events - * will make infinite loops (AIScript). - */ -static void AI_PutCommandInQueue(PlayerID player, TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCallback* callback) -{ - AICommand *com; - - if (_ai_player[player].queue_tail == NULL) { - /* There is no item in the queue yet, create the queue */ - _ai_player[player].queue = malloc(sizeof(AICommand)); - _ai_player[player].queue_tail = _ai_player[player].queue; - } else { - /* Add an item at the end */ - _ai_player[player].queue_tail->next = malloc(sizeof(AICommand)); - _ai_player[player].queue_tail = _ai_player[player].queue_tail->next; - } - - /* This is our new item */ - com = _ai_player[player].queue_tail; - - /* Assign the info */ - com->tile = tile; - com->p1 = p1; - com->p2 = p2; - com->procc = procc; - com->callback = callback; - com->next = NULL; - com->text = NULL; - - /* Copy the cmd_text, if needed */ - if (_cmd_text != NULL) { - com->text = strdup(_cmd_text); - _cmd_text = NULL; - } -} - -/** - * Executes a raw DoCommand for the AI. - */ -int32 AI_DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, CommandCallback* callback) -{ - PlayerID old_lp; - int32 res = 0; - const char* tmp_cmdtext; - - /* If you enable DC_EXEC with DC_QUERY_COST you are a really strange - * person.. should we check for those funny jokes? - */ - - /* The test already resets _cmd_text, so backup the pointer */ - tmp_cmdtext = _cmd_text; - - /* First, do a test-run to see if we can do this */ - res = DoCommand(tile, p1, p2, flags & ~DC_EXEC, procc); - /* The command failed, or you didn't want to execute, or you are quering, return */ - if (CmdFailed(res) || !(flags & DC_EXEC) || (flags & DC_QUERY_COST)) { - return res; - } - - /* Restore _cmd_text */ - _cmd_text = tmp_cmdtext; - - /* If we did a DC_EXEC, and the command did not return an error, execute it - * over the network */ - if (flags & DC_AUTO) procc |= CMD_AUTO; - if (flags & DC_NO_WATER) procc |= CMD_NO_WATER; - - /* NetworkSend_Command needs _local_player to be set correctly, so - * adjust it, and put it back right after the function */ - old_lp = _local_player; - _local_player = _current_player; - -#ifdef ENABLE_NETWORK - /* Send the command */ - if (_networking) { - /* Network is easy, send it to his handler */ - NetworkSend_Command(tile, p1, p2, procc, callback); - } else { -#else - { -#endif - /* If we execute BuildCommands directly in SP, we have a big problem with events - * so we need to delay is for 1 tick */ - AI_PutCommandInQueue(_current_player, tile, p1, p2, procc, callback); - } - - /* Set _local_player back */ - _local_player = old_lp; - - return res; -} - - -int32 AI_DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) -{ - return AI_DoCommandCc(tile, p1, p2, flags, procc, NULL); -} - - -/** - * Run 1 tick of the AI. Don't overdo it, keep it realistic. - */ -static void AI_RunTick(PlayerID player) -{ - extern void AiNewDoGameLoop(Player *p); - - Player *p = GetPlayer(player); - _current_player = player; - - if (_patches.ainew_active) { - AiNewDoGameLoop(p); - } else { - /* Enable all kind of cheats the old AI needs in order to operate correctly... */ - _is_old_ai_player = true; - AiDoGameLoop(p); - _is_old_ai_player = false; - } -} - - -/** - * The gameloop for AIs. - * Handles one tick for all the AIs. - */ -void AI_RunGameLoop(void) -{ - /* Don't do anything if ai is disabled */ - if (!_ai.enabled) return; - - /* Don't do anything if we are a network-client, or the AI has been disabled */ - if (_networking && (!_network_server || !_patches.ai_in_multiplayer)) return; - - /* New tick */ - _ai.tick++; - - /* Make sure the AI follows the difficulty rule.. */ - assert(_opt.diff.competitor_speed <= 4); - if ((_ai.tick & ((1 << (4 - _opt.diff.competitor_speed)) - 1)) != 0) return; - - /* Check for AI-client (so joining a network with an AI) */ - if (!_networking || _network_server) { - /* Check if we want to run AIs (server or SP only) */ - const Player* p; - - FOR_ALL_PLAYERS(p) { - if (p->is_active && p->is_ai) { - /* This should always be true, else something went wrong... */ - assert(_ai_player[p->index].active); - - /* Run the script */ - AI_DequeueCommands(p->index); - AI_RunTick(p->index); - } - } - } - - _current_player = OWNER_NONE; -} - -/** - * A new AI sees the day of light. You can do here what ever you think is needed. - */ -void AI_StartNewAI(PlayerID player) -{ - assert(IsValidPlayer(player)); - - /* Called if a new AI is booted */ - _ai_player[player].active = true; -} - -/** - * This AI player died. Give it some chance to make a final puf. - */ -void AI_PlayerDied(PlayerID player) -{ - /* Called if this AI died */ - _ai_player[player].active = false; -} - -/** - * Initialize some AI-related stuff. - */ -void AI_Initialize(void) -{ - /* First, make sure all AIs are DEAD! */ - AI_Uninitialize(); - - memset(&_ai, 0, sizeof(_ai)); - memset(&_ai_player, 0, sizeof(_ai_player)); - - _ai.enabled = true; -} - -/** - * Deinitializer for AI-related stuff. - */ -void AI_Uninitialize(void) -{ - const Player* p; - - FOR_ALL_PLAYERS(p) { - if (p->is_active && p->is_ai) AI_PlayerDied(p->index); - } -} diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp new file mode 100644 --- /dev/null +++ b/src/ai/ai.cpp @@ -0,0 +1,247 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../variables.h" +#include "../command.h" +#include "../network/network.h" +#include "ai.h" +#include "default/default.h" + +/** + * Dequeues commands put in the queue via AI_PutCommandInQueue. + */ +static void AI_DequeueCommands(PlayerID player) +{ + AICommand *com, *entry_com; + + entry_com = _ai_player[player].queue; + + /* It happens that DoCommandP issues a new DoCommandAI which adds a new command + * to this very same queue (don't argue about this, if it currently doesn't + * happen I can tell you it will happen with AIScript -- TrueLight). If we + * do not make the queue NULL, that commands will be dequeued immediatly. + * Therefor we safe the entry-point to entry_com, and make the queue NULL, so + * the new queue can be safely built up. */ + _ai_player[player].queue = NULL; + _ai_player[player].queue_tail = NULL; + + /* Dequeue all commands */ + while ((com = entry_com) != NULL) { + _current_player = player; + + _cmd_text = com->text; + DoCommandP(com->tile, com->p1, com->p2, com->callback, com->procc); + + /* Free item */ + entry_com = com->next; + free(com->text); + free(com); + } +} + +/** + * Needed for SP; we need to delay DoCommand with 1 tick, because else events + * will make infinite loops (AIScript). + */ +static void AI_PutCommandInQueue(PlayerID player, TileIndex tile, uint32 p1, uint32 p2, uint procc, CommandCallback* callback) +{ + AICommand *com; + + if (_ai_player[player].queue_tail == NULL) { + /* There is no item in the queue yet, create the queue */ + _ai_player[player].queue = malloc(sizeof(AICommand)); + _ai_player[player].queue_tail = _ai_player[player].queue; + } else { + /* Add an item at the end */ + _ai_player[player].queue_tail->next = malloc(sizeof(AICommand)); + _ai_player[player].queue_tail = _ai_player[player].queue_tail->next; + } + + /* This is our new item */ + com = _ai_player[player].queue_tail; + + /* Assign the info */ + com->tile = tile; + com->p1 = p1; + com->p2 = p2; + com->procc = procc; + com->callback = callback; + com->next = NULL; + com->text = NULL; + + /* Copy the cmd_text, if needed */ + if (_cmd_text != NULL) { + com->text = strdup(_cmd_text); + _cmd_text = NULL; + } +} + +/** + * Executes a raw DoCommand for the AI. + */ +int32 AI_DoCommandCc(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc, CommandCallback* callback) +{ + PlayerID old_lp; + int32 res = 0; + const char* tmp_cmdtext; + + /* If you enable DC_EXEC with DC_QUERY_COST you are a really strange + * person.. should we check for those funny jokes? + */ + + /* The test already resets _cmd_text, so backup the pointer */ + tmp_cmdtext = _cmd_text; + + /* First, do a test-run to see if we can do this */ + res = DoCommand(tile, p1, p2, flags & ~DC_EXEC, procc); + /* The command failed, or you didn't want to execute, or you are quering, return */ + if (CmdFailed(res) || !(flags & DC_EXEC) || (flags & DC_QUERY_COST)) { + return res; + } + + /* Restore _cmd_text */ + _cmd_text = tmp_cmdtext; + + /* If we did a DC_EXEC, and the command did not return an error, execute it + * over the network */ + if (flags & DC_AUTO) procc |= CMD_AUTO; + if (flags & DC_NO_WATER) procc |= CMD_NO_WATER; + + /* NetworkSend_Command needs _local_player to be set correctly, so + * adjust it, and put it back right after the function */ + old_lp = _local_player; + _local_player = _current_player; + +#ifdef ENABLE_NETWORK + /* Send the command */ + if (_networking) { + /* Network is easy, send it to his handler */ + NetworkSend_Command(tile, p1, p2, procc, callback); + } else { +#else + { +#endif + /* If we execute BuildCommands directly in SP, we have a big problem with events + * so we need to delay is for 1 tick */ + AI_PutCommandInQueue(_current_player, tile, p1, p2, procc, callback); + } + + /* Set _local_player back */ + _local_player = old_lp; + + return res; +} + + +int32 AI_DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) +{ + return AI_DoCommandCc(tile, p1, p2, flags, procc, NULL); +} + + +/** + * Run 1 tick of the AI. Don't overdo it, keep it realistic. + */ +static void AI_RunTick(PlayerID player) +{ + extern void AiNewDoGameLoop(Player *p); + + Player *p = GetPlayer(player); + _current_player = player; + + if (_patches.ainew_active) { + AiNewDoGameLoop(p); + } else { + /* Enable all kind of cheats the old AI needs in order to operate correctly... */ + _is_old_ai_player = true; + AiDoGameLoop(p); + _is_old_ai_player = false; + } +} + + +/** + * The gameloop for AIs. + * Handles one tick for all the AIs. + */ +void AI_RunGameLoop(void) +{ + /* Don't do anything if ai is disabled */ + if (!_ai.enabled) return; + + /* Don't do anything if we are a network-client, or the AI has been disabled */ + if (_networking && (!_network_server || !_patches.ai_in_multiplayer)) return; + + /* New tick */ + _ai.tick++; + + /* Make sure the AI follows the difficulty rule.. */ + assert(_opt.diff.competitor_speed <= 4); + if ((_ai.tick & ((1 << (4 - _opt.diff.competitor_speed)) - 1)) != 0) return; + + /* Check for AI-client (so joining a network with an AI) */ + if (!_networking || _network_server) { + /* Check if we want to run AIs (server or SP only) */ + const Player* p; + + FOR_ALL_PLAYERS(p) { + if (p->is_active && p->is_ai) { + /* This should always be true, else something went wrong... */ + assert(_ai_player[p->index].active); + + /* Run the script */ + AI_DequeueCommands(p->index); + AI_RunTick(p->index); + } + } + } + + _current_player = OWNER_NONE; +} + +/** + * A new AI sees the day of light. You can do here what ever you think is needed. + */ +void AI_StartNewAI(PlayerID player) +{ + assert(IsValidPlayer(player)); + + /* Called if a new AI is booted */ + _ai_player[player].active = true; +} + +/** + * This AI player died. Give it some chance to make a final puf. + */ +void AI_PlayerDied(PlayerID player) +{ + /* Called if this AI died */ + _ai_player[player].active = false; +} + +/** + * Initialize some AI-related stuff. + */ +void AI_Initialize(void) +{ + /* First, make sure all AIs are DEAD! */ + AI_Uninitialize(); + + memset(&_ai, 0, sizeof(_ai)); + memset(&_ai_player, 0, sizeof(_ai_player)); + + _ai.enabled = true; +} + +/** + * Deinitializer for AI-related stuff. + */ +void AI_Uninitialize(void) +{ + const Player* p; + + FOR_ALL_PLAYERS(p) { + if (p->is_active && p->is_ai) AI_PlayerDied(p->index); + } +} diff --git a/src/ai/default/default.c b/src/ai/default/default.c deleted file mode 100644 --- a/src/ai/default/default.c +++ /dev/null @@ -1,3934 +0,0 @@ -/* $Id$ */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../aircraft.h" -#include "../../bridge_map.h" -#include "../../functions.h" -#include "../../map.h" -#include "../../rail_map.h" -#include "../../road_map.h" -#include "../../roadveh.h" -#include "../../station_map.h" -#include "../../tile.h" -#include "../../player.h" -#include "../../tunnel_map.h" -#include "../../vehicle.h" -#include "../../engine.h" -#include "../../command.h" -#include "../../town.h" -#include "../../industry.h" -#include "../../station.h" -#include "../../pathfind.h" -#include "../../economy.h" -#include "../../airport.h" -#include "../../depot.h" -#include "../../variables.h" -#include "../../bridge.h" -#include "../../date.h" -#include "default.h" - -// remove some day perhaps? -static uint _ai_service_interval; - -typedef void AiStateAction(Player *p); - -enum { - AIS_0 = 0, - AIS_1 = 1, - AIS_VEH_LOOP = 2, - AIS_VEH_CHECK_REPLACE_VEHICLE = 3, - AIS_VEH_DO_REPLACE_VEHICLE = 4, - AIS_WANT_NEW_ROUTE = 5, - AIS_BUILD_DEFAULT_RAIL_BLOCKS = 6, - AIS_BUILD_RAIL = 7, - AIS_BUILD_RAIL_VEH = 8, - AIS_DELETE_RAIL_BLOCKS = 9, - AIS_BUILD_DEFAULT_ROAD_BLOCKS = 10, - AIS_BUILD_ROAD = 11, - AIS_BUILD_ROAD_VEHICLES = 12, - AIS_DELETE_ROAD_BLOCKS = 13, - AIS_AIRPORT_STUFF = 14, - AIS_BUILD_DEFAULT_AIRPORT_BLOCKS = 15, - AIS_BUILD_AIRCRAFT_VEHICLES = 16, - AIS_CHECK_SHIP_STUFF = 17, - AIS_BUILD_DEFAULT_SHIP_BLOCKS = 18, - AIS_DO_SHIP_STUFF = 19, - AIS_SELL_VEHICLE = 20, - AIS_REMOVE_STATION = 21, - AIS_REMOVE_TRACK = 22, - AIS_REMOVE_SINGLE_RAIL_TILE = 23 -}; - - -#include "../../table/ai_rail.h" - -static byte GetRailTrackStatus(TileIndex tile) -{ - uint32 r = GetTileTrackStatus(tile, TRANSPORT_RAIL); - return (byte) (r | r >> 8); -} - - -static void AiCase0(Player *p) -{ - p->ai.state = AIS_REMOVE_TRACK; - p->ai.state_counter = 0; -} - -static void AiCase1(Player *p) -{ - p->ai.cur_veh = NULL; - p->ai.state = AIS_VEH_LOOP; -} - -static void AiStateVehLoop(Player *p) -{ - Vehicle *v; - uint index; - - index = (p->ai.cur_veh == NULL) ? 0 : p->ai.cur_veh->index + 1; - - FOR_ALL_VEHICLES_FROM(v, index) { - if (v->owner != _current_player) continue; - - if ((v->type == VEH_Train && v->subtype == 0) || - v->type == VEH_Road || - (v->type == VEH_Aircraft && v->subtype <= 2) || - v->type == VEH_Ship) { - /* replace engine? */ - if (v->type == VEH_Train && v->engine_type < 3 && - (_price.build_railvehicle >> 3) < p->player_money) { - p->ai.state = AIS_VEH_CHECK_REPLACE_VEHICLE; - p->ai.cur_veh = v; - return; - } - - /* not profitable? */ - if (v->age >= 730 && - v->profit_last_year < _price.station_value * 5 && - v->profit_this_year < _price.station_value * 5) { - p->ai.state_counter = 0; - p->ai.state = AIS_SELL_VEHICLE; - p->ai.cur_veh = v; - return; - } - - /* not reliable? */ - if (v->age >= v->max_age || ( - v->age != 0 && - GetEngine(v->engine_type)->reliability < 35389 - )) { - p->ai.state = AIS_VEH_CHECK_REPLACE_VEHICLE; - p->ai.cur_veh = v; - return; - } - } - } - - p->ai.state = AIS_WANT_NEW_ROUTE; - p->ai.state_counter = 0; -} - -static EngineID AiChooseTrainToBuild(RailType railtype, int32 money, byte flag, TileIndex tile) -{ - EngineID best_veh_index = INVALID_ENGINE; - byte best_veh_score = 0; - int32 ret; - EngineID i; - - for (i = 0; i < NUM_TRAIN_ENGINES; i++) { - const RailVehicleInfo *rvi = RailVehInfo(i); - const Engine* e = GetEngine(i); - - if (!IsCompatibleRail(e->railtype, railtype) || - rvi->flags & RVI_WAGON || - (rvi->flags & RVI_MULTIHEAD && flag & 1) || - !HASBIT(e->player_avail, _current_player) || - e->reliability < 0x8A3D) { - continue; - } - - ret = DoCommand(tile, i, 0, 0, CMD_BUILD_RAIL_VEHICLE); - if (!CmdFailed(ret) && ret <= money && rvi->ai_rank >= best_veh_score) { - best_veh_score = rvi->ai_rank; - best_veh_index = i; - } - } - - return best_veh_index; -} - -static EngineID AiChooseRoadVehToBuild(CargoID cargo, int32 money, TileIndex tile) -{ - EngineID best_veh_index = INVALID_ENGINE; - int32 best_veh_rating = 0; - EngineID i = ROAD_ENGINES_INDEX; - EngineID end = i + NUM_ROAD_ENGINES; - - for (; i != end; i++) { - const RoadVehicleInfo *rvi = RoadVehInfo(i); - const Engine* e = GetEngine(i); - int32 rating; - int32 ret; - - if (!HASBIT(e->player_avail, _current_player) || e->reliability < 0x8A3D) { - continue; - } - - /* Skip vehicles which can't take our cargo type */ - if (rvi->cargo_type != cargo && !CanRefitTo(i, cargo)) continue; - - /* Rate and compare the engine by speed & capacity */ - rating = rvi->max_speed * rvi->capacity; - if (rating <= best_veh_rating) continue; - - ret = DoCommand(tile, i, 0, 0, CMD_BUILD_ROAD_VEH); - if (CmdFailed(ret)) continue; - - /* Add the cost of refitting */ - if (rvi->cargo_type != cargo) ret += GetRefitCost(i); - if (ret > money) continue; - - best_veh_rating = rating; - best_veh_index = i; - } - - return best_veh_index; -} - -static EngineID AiChooseAircraftToBuild(int32 money, byte flag) -{ - EngineID best_veh_index = INVALID_ENGINE; - int32 best_veh_cost = 0; - EngineID i; - - for (i = AIRCRAFT_ENGINES_INDEX; i != AIRCRAFT_ENGINES_INDEX + NUM_AIRCRAFT_ENGINES; i++) { - const Engine* e = GetEngine(i); - int32 ret; - - if (!HASBIT(e->player_avail, _current_player) || e->reliability < 0x8A3D) { - continue; - } - - if ((AircraftVehInfo(i)->subtype & AIR_CTOL) != flag) continue; - - ret = DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_AIRCRAFT); - if (!CmdFailed(ret) && ret <= money && ret >= best_veh_cost) { - best_veh_cost = ret; - best_veh_index = i; - } - } - - return best_veh_index; -} - -static int32 AiGetBasePrice(const Player* p) -{ - int32 base = _price.station_value; - - // adjust base price when more expensive vehicles are available - switch (p->ai.railtype_to_use) { - default: NOT_REACHED(); - case RAILTYPE_RAIL: break; - case RAILTYPE_ELECTRIC: break; - case RAILTYPE_MONO: base = (base * 3) >> 1; break; - case RAILTYPE_MAGLEV: base *= 2; break; - } - - return base; -} - -#if 0 -static EngineID AiChooseShipToBuild(byte cargo, int32 money) -{ - // XXX: not done - return INVALID_ENGINE; -} -#endif - -static EngineID AiChooseRoadVehToReplaceWith(const Player* p, const Vehicle* v) -{ - int32 avail_money = p->player_money + v->value; - return AiChooseRoadVehToBuild(v->cargo_type, avail_money, v->tile); -} - -static EngineID AiChooseAircraftToReplaceWith(const Player* p, const Vehicle* v) -{ - int32 avail_money = p->player_money + v->value; - return AiChooseAircraftToBuild( - avail_money, AircraftVehInfo(v->engine_type)->subtype & AIR_CTOL - ); -} - -static EngineID AiChooseTrainToReplaceWith(const Player* p, const Vehicle* v) -{ - int32 avail_money = p->player_money + v->value; - const Vehicle* u = v; - int num = 0; - - while (++num, u->next != NULL) { - u = u->next; - } - - // XXX: check if a wagon - return AiChooseTrainToBuild(v->u.rail.railtype, avail_money, 0, v->tile); -} - -static EngineID AiChooseShipToReplaceWith(const Player* p, const Vehicle* v) -{ - error("!AiChooseShipToReplaceWith"); - - /* maybe useless, but avoids compiler warning this way */ - return INVALID_ENGINE; -} - -static void AiHandleGotoDepot(Player *p, int cmd) -{ - if (p->ai.cur_veh->current_order.type != OT_GOTO_DEPOT) - DoCommand(0, p->ai.cur_veh->index, 0, DC_EXEC, cmd); - - if (++p->ai.state_counter <= 1387) { - p->ai.state = AIS_VEH_DO_REPLACE_VEHICLE; - return; - } - - if (p->ai.cur_veh->current_order.type == OT_GOTO_DEPOT) { - p->ai.cur_veh->current_order.type = OT_DUMMY; - p->ai.cur_veh->current_order.flags = 0; - InvalidateWindow(WC_VEHICLE_VIEW, p->ai.cur_veh->index); - } -} - -static void AiRestoreVehicleOrders(Vehicle *v, BackuppedOrders *bak) -{ - uint i; - - for (i = 0; bak->order[i].type != OT_NOTHING; i++) { - if (!DoCommandP(0, v->index + (i << 16), PackOrder(&bak->order[i]), NULL, CMD_INSERT_ORDER | CMD_NO_TEST_IF_IN_NETWORK)) - break; - } -} - -static void AiHandleReplaceTrain(Player *p) -{ - const Vehicle* v = p->ai.cur_veh; - BackuppedOrders orderbak[1]; - EngineID veh; - - // wait until the vehicle reaches the depot. - if (!IsTileDepotType(v->tile, TRANSPORT_RAIL) || v->u.rail.track != 0x80 || !(v->vehstatus&VS_STOPPED)) { - AiHandleGotoDepot(p, CMD_SEND_TRAIN_TO_DEPOT); - return; - } - - veh = AiChooseTrainToReplaceWith(p, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, orderbak); - tile = v->tile; - - if (!CmdFailed(DoCommand(0, v->index, 2, DC_EXEC, CMD_SELL_RAIL_WAGON)) && - !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE))) { - VehicleID veh = _new_vehicle_id; - AiRestoreVehicleOrders(GetVehicle(veh), orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_TRAIN); - - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceRoadVeh(Player *p) -{ - const Vehicle* v = p->ai.cur_veh; - BackuppedOrders orderbak[1]; - EngineID veh; - - if (!IsRoadVehInDepotStopped(v)) { - AiHandleGotoDepot(p, CMD_SEND_ROADVEH_TO_DEPOT); - return; - } - - veh = AiChooseRoadVehToReplaceWith(p, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, orderbak); - tile = v->tile; - - if (!CmdFailed(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH)) && - !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) { - VehicleID veh = _new_vehicle_id; - - AiRestoreVehicleOrders(GetVehicle(veh), orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_ROADVEH); - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceAircraft(Player *p) -{ - const Vehicle* v = p->ai.cur_veh; - BackuppedOrders orderbak[1]; - EngineID veh; - - if (!IsAircraftInHangarStopped(v)) { - AiHandleGotoDepot(p, CMD_SEND_AIRCRAFT_TO_HANGAR); - return; - } - - veh = AiChooseAircraftToReplaceWith(p, v); - if (veh != INVALID_ENGINE) { - TileIndex tile; - - BackupVehicleOrders(v, orderbak); - tile = v->tile; - - if (!CmdFailed(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT)) && - !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) { - VehicleID veh = _new_vehicle_id; - AiRestoreVehicleOrders(GetVehicle(veh), orderbak); - DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_AIRCRAFT); - - DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - } - } -} - -static void AiHandleReplaceShip(Player *p) -{ - error("!AiHandleReplaceShip"); -} - -typedef EngineID CheckReplaceProc(const Player* p, const Vehicle* v); - -static CheckReplaceProc* const _veh_check_replace_proc[] = { - AiChooseTrainToReplaceWith, - AiChooseRoadVehToReplaceWith, - AiChooseShipToReplaceWith, - AiChooseAircraftToReplaceWith, -}; - -typedef void DoReplaceProc(Player *p); -static DoReplaceProc* const _veh_do_replace_proc[] = { - AiHandleReplaceTrain, - AiHandleReplaceRoadVeh, - AiHandleReplaceShip, - AiHandleReplaceAircraft -}; - -static void AiStateCheckReplaceVehicle(Player *p) -{ - const Vehicle* v = p->ai.cur_veh; - - if (!IsValidVehicle(v) || - v->owner != _current_player || - v->type > VEH_Ship || - _veh_check_replace_proc[v->type - VEH_Train](p, v) == INVALID_ENGINE) { - p->ai.state = AIS_VEH_LOOP; - } else { - p->ai.state_counter = 0; - p->ai.state = AIS_VEH_DO_REPLACE_VEHICLE; - } -} - -static void AiStateDoReplaceVehicle(Player *p) -{ - const Vehicle* v = p->ai.cur_veh; - - p->ai.state = AIS_VEH_LOOP; - // vehicle is not owned by the player anymore, something went very wrong. - if (!IsValidVehicle(v) || v->owner != _current_player) return; - _veh_do_replace_proc[v->type - VEH_Train](p); -} - -typedef struct FoundRoute { - int distance; - CargoID cargo; - void *from; - void *to; -} FoundRoute; - -static Town *AiFindRandomTown(void) -{ - return GetRandomTown(); -} - -static Industry *AiFindRandomIndustry(void) -{ - return GetRandomIndustry(); -} - -static void AiFindSubsidyIndustryRoute(FoundRoute *fr) -{ - uint i; - CargoID cargo; - const Subsidy* s; - Industry* from; - TileIndex to_xy; - - // initially error - fr->distance = -1; - - // Randomize subsidy index.. - i = RandomRange(lengthof(_subsidies) * 3); - if (i >= lengthof(_subsidies)) return; - - s = &_subsidies[i]; - - // Don't want passengers or mail - cargo = s->cargo_type; - if (cargo == CT_INVALID || - cargo == CT_PASSENGERS || - cargo == CT_MAIL || - s->age > 7) { - return; - } - fr->cargo = cargo; - - fr->from = from = GetIndustry(s->from); - - if (cargo == CT_GOODS || cargo == CT_FOOD) { - Town* to_tow = GetTown(s->to); - - if (to_tow->population < (cargo == CT_FOOD ? 200U : 900U)) return; // error - fr->to = to_tow; - to_xy = to_tow->xy; - } else { - Industry* to_ind = GetIndustry(s->to); - - fr->to = to_ind; - to_xy = to_ind->xy; - } - - fr->distance = DistanceManhattan(from->xy, to_xy); -} - -static void AiFindSubsidyPassengerRoute(FoundRoute *fr) -{ - uint i; - const Subsidy* s; - Town *from,*to; - - // initially error - fr->distance = -1; - - // Randomize subsidy index.. - i = RandomRange(lengthof(_subsidies) * 3); - if (i >= lengthof(_subsidies)) return; - - s = &_subsidies[i]; - - // Only want passengers - if (s->cargo_type != CT_PASSENGERS || s->age > 7) return; - fr->cargo = s->cargo_type; - - fr->from = from = GetTown(s->from); - fr->to = to = GetTown(s->to); - - // They must be big enough - if (from->population < 400 || to->population < 400) return; - - fr->distance = DistanceManhattan(from->xy, to->xy); -} - -static void AiFindRandomIndustryRoute(FoundRoute *fr) -{ - Industry* i; - uint32 r; - CargoID cargo; - - // initially error - fr->distance = -1; - - r = Random(); - - // pick a source - fr->from = i = AiFindRandomIndustry(); - if (i == NULL) return; - - // pick a random produced cargo - cargo = i->produced_cargo[0]; - if (r & 1 && i->produced_cargo[1] != CT_INVALID) cargo = i->produced_cargo[1]; - - fr->cargo = cargo; - - // don't allow passengers - if (cargo == CT_INVALID || cargo == CT_PASSENGERS) return; - - if (cargo != CT_GOODS && cargo != CT_FOOD) { - // pick a dest, and see if it can receive - Industry* i2 = AiFindRandomIndustry(); - - if (i2 == NULL || i == i2 || ( - i2->accepts_cargo[0] != cargo && - i2->accepts_cargo[1] != cargo && - i2->accepts_cargo[2] != cargo) - ) { - return; - } - - fr->to = i2; - fr->distance = DistanceManhattan(i->xy, i2->xy); - } else { - // pick a dest town, and see if it's big enough - Town* t = AiFindRandomTown(); - - if (t == NULL || t->population < (cargo == CT_FOOD ? 200U : 900U)) return; - - fr->to = t; - fr->distance = DistanceManhattan(i->xy, t->xy); - } -} - -static void AiFindRandomPassengerRoute(FoundRoute *fr) -{ - Town* source; - Town* dest; - - // initially error - fr->distance = -1; - - fr->from = source = AiFindRandomTown(); - if (source == NULL || source->population < 400) return; - - fr->to = dest = AiFindRandomTown(); - if (dest == NULL || source == dest || dest->population < 400) return; - - fr->distance = DistanceManhattan(source->xy, dest->xy); -} - -// Warn: depends on 'xy' being the first element in both Town and Industry -#define GET_TOWN_OR_INDUSTRY_TILE(p) (((Town*)(p))->xy) - -static bool AiCheckIfRouteIsGood(Player *p, FoundRoute *fr, byte bitmask) -{ - TileIndex from_tile, to_tile; - Station *st; - int dist; - uint same_station = 0; - - // Make sure distance to closest station is < 37 pixels. - from_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->from); - to_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->to); - - dist = 0xFFFF; - FOR_ALL_STATIONS(st) { - int cur; - - if (st->owner != _current_player) continue; - cur = DistanceMax(from_tile, st->xy); - if (cur < dist) dist = cur; - cur = DistanceMax(to_tile, st->xy); - if (cur < dist) dist = cur; - if (to_tile == from_tile && st->xy == to_tile) same_station++; - } - - // To prevent the AI from building ten busstations in the same town, do some calculations - // For each road or airport station, we want 350 of population! - if ((bitmask == 2 || bitmask == 4) && - same_station > 2 && - ((Town*)fr->from)->population < same_station * 350) { - return false; - } - - if (dist != 0xFFFF && dist > 37) return false; - - if (p->ai.route_type_mask != 0 && - !(p->ai.route_type_mask & bitmask) && - !CHANCE16(1, 5)) { - return false; - } - - if (fr->cargo == CT_PASSENGERS || fr->cargo == CT_MAIL) { - const Town* from = fr->from; - const Town* to = fr->to; - - if (from->pct_pass_transported > 0x99 || - to->pct_pass_transported > 0x99) { - return false; - } - - // Make sure it has a reasonably good rating - if (from->ratings[_current_player] < -100 || - to->ratings[_current_player] < -100) { - return false; - } - } else { - const Industry* i = (const Industry*)fr->from; - - if (i->pct_transported[fr->cargo != i->produced_cargo[0]] > 0x99 || - i->total_production[fr->cargo != i->produced_cargo[0]] == 0) { - return false; - } - } - - p->ai.route_type_mask |= bitmask; - return true; -} - -static byte AiGetDirectionBetweenTiles(TileIndex a, TileIndex b) -{ - byte i = (TileX(a) < TileX(b)) ? 1 : 0; - if (TileY(a) >= TileY(b)) i ^= 3; - return i; -} - -static TileIndex AiGetPctTileBetween(TileIndex a, TileIndex b, byte pct) -{ - return TileXY( - TileX(a) + ((TileX(b) - TileX(a)) * pct >> 8), - TileY(a) + ((TileY(b) - TileY(a)) * pct >> 8) - ); -} - -static void AiWantLongIndustryRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 60, 90 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 60, 90 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; - - // Fill the source field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 9; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.unk6 = 1; - p->ai.src.unk7 = 0; - p->ai.src.buildcmd_a = 0x24; - p->ai.src.buildcmd_b = 0xFF; - p->ai.src.direction = AiGetDirectionBetweenTiles( - p->ai.src.spec_tile, - p->ai.dst.spec_tile - ); - p->ai.src.cargo = fr.cargo | 0x80; - - // Fill the dest field - - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 9; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.unk6 = 1; - p->ai.dst.unk7 = 0; - p->ai.dst.buildcmd_a = 0x34; - p->ai.dst.buildcmd_b = 0xFF; - p->ai.dst.direction = AiGetDirectionBetweenTiles( - p->ai.dst.spec_tile, - p->ai.src.spec_tile - ); - p->ai.dst.cargo = fr.cargo; - - // Fill middle field 1 - p->ai.mid1.spec_tile = AiGetPctTileBetween( - p->ai.src.spec_tile, - p->ai.dst.spec_tile, - 0x55 - ); - p->ai.mid1.use_tile = 0; - p->ai.mid1.rand_rng = 6; - p->ai.mid1.cur_building_rule = 0xFF; - p->ai.mid1.unk6 = 2; - p->ai.mid1.unk7 = 1; - p->ai.mid1.buildcmd_a = 0x30; - p->ai.mid1.buildcmd_b = 0xFF; - p->ai.mid1.direction = p->ai.src.direction; - p->ai.mid1.cargo = fr.cargo; - - // Fill middle field 2 - p->ai.mid2.spec_tile = AiGetPctTileBetween( - p->ai.src.spec_tile, - p->ai.dst.spec_tile, - 0xAA - ); - p->ai.mid2.use_tile = 0; - p->ai.mid2.rand_rng = 6; - p->ai.mid2.cur_building_rule = 0xFF; - p->ai.mid2.unk6 = 2; - p->ai.mid2.unk7 = 1; - p->ai.mid2.buildcmd_a = 0xFF; - p->ai.mid2.buildcmd_b = 0xFF; - p->ai.mid2.direction = p->ai.dst.direction; - p->ai.mid2.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_wagons = 3; - p->ai.build_kind = 2; - p->ai.num_build_rec = 4; - p->ai.num_loco_to_build = 2; - p->ai.num_want_fullload = 2; - p->ai.wagon_list[0] = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantMediumIndustryRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 40, 60 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 40, 60 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 9; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.unk6 = 1; - p->ai.src.unk7 = 0; - p->ai.src.buildcmd_a = 0x10; - p->ai.src.buildcmd_b = 0xFF; - p->ai.src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - p->ai.src.cargo = fr.cargo | 0x80; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 9; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.unk6 = 1; - p->ai.dst.unk7 = 0; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.buildcmd_b = 0xFF; - p->ai.dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_wagons = 3; - p->ai.build_kind = 1; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 1; - p->ai.num_want_fullload = 1; - p->ai.wagon_list[0] = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantShortIndustryRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 9; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.unk6 = 1; - p->ai.src.unk7 = 0; - p->ai.src.buildcmd_a = 0x10; - p->ai.src.buildcmd_b = 0xFF; - p->ai.src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - p->ai.src.cargo = fr.cargo | 0x80; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 9; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.unk6 = 1; - p->ai.dst.unk7 = 0; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.buildcmd_b = 0xFF; - p->ai.dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_wagons = 2; - p->ai.build_kind = 1; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 1; - p->ai.num_want_fullload = 1; - p->ai.wagon_list[0] = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantMailRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 60, 110 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 60, 110 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_MAIL; - if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 7; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.unk6 = 1; - p->ai.src.unk7 = 0; - p->ai.src.buildcmd_a = 0x24; - p->ai.src.buildcmd_b = 0xFF; - p->ai.src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - p->ai.src.cargo = fr.cargo; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 7; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.unk6 = 1; - p->ai.dst.unk7 = 0; - p->ai.dst.buildcmd_a = 0x34; - p->ai.dst.buildcmd_b = 0xFF; - p->ai.dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - p->ai.dst.cargo = fr.cargo; - - // Fill middle field 1 - p->ai.mid1.spec_tile = AiGetPctTileBetween( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - 0x55 - ); - p->ai.mid1.use_tile = 0; - p->ai.mid1.rand_rng = 6; - p->ai.mid1.cur_building_rule = 0xFF; - p->ai.mid1.unk6 = 2; - p->ai.mid1.unk7 = 1; - p->ai.mid1.buildcmd_a = 0x30; - p->ai.mid1.buildcmd_b = 0xFF; - p->ai.mid1.direction = p->ai.src.direction; - p->ai.mid1.cargo = fr.cargo; - - // Fill middle field 2 - p->ai.mid2.spec_tile = AiGetPctTileBetween( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - 0xAA - ); - p->ai.mid2.use_tile = 0; - p->ai.mid2.rand_rng = 6; - p->ai.mid2.cur_building_rule = 0xFF; - p->ai.mid2.unk6 = 2; - p->ai.mid2.unk7 = 1; - p->ai.mid2.buildcmd_a = 0xFF; - p->ai.mid2.buildcmd_b = 0xFF; - p->ai.mid2.direction = p->ai.dst.direction; - p->ai.mid2.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_wagons = 3; - p->ai.build_kind = 2; - p->ai.num_build_rec = 4; - p->ai.num_loco_to_build = 2; - p->ai.num_want_fullload = 0; - p->ai.wagon_list[0] = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantPassengerRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 0, 55 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 0, 55 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 7; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.unk6 = 1; - p->ai.src.unk7 = 0; - p->ai.src.buildcmd_a = 0x10; - p->ai.src.buildcmd_b = 0xFF; - p->ai.src.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.from), - GET_TOWN_OR_INDUSTRY_TILE(fr.to) - ); - p->ai.src.cargo = fr.cargo; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 7; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.unk6 = 1; - p->ai.dst.unk7 = 0; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.buildcmd_b = 0xFF; - p->ai.dst.direction = AiGetDirectionBetweenTiles( - GET_TOWN_OR_INDUSTRY_TILE(fr.to), - GET_TOWN_OR_INDUSTRY_TILE(fr.from) - ); - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_wagons = 2; - p->ai.build_kind = 1; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 1; - p->ai.num_want_fullload = 0; - p->ai.wagon_list[0] = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantTrainRoute(Player *p) -{ - uint16 r = GB(Random(), 0, 16); - - p->ai.railtype_to_use = GetBestRailtype(p); - - if (r > 0xD000) { - AiWantLongIndustryRoute(p); - } else if (r > 0x6000) { - AiWantMediumIndustryRoute(p); - } else if (r > 0x1000) { - AiWantShortIndustryRoute(p); - } else if (r > 0x800) { - AiWantPassengerRoute(p); - } else { - AiWantMailRoute(p); - } -} - -static void AiWantLongRoadIndustryRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 35, 55 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 35, 55 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 9; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.buildcmd_a = 1; - p->ai.src.direction = 0; - p->ai.src.cargo = fr.cargo | 0x80; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 9; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.direction = 0; - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 5; - p->ai.num_want_fullload = 5; - -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantMediumRoadIndustryRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; - - // try a random one - AiFindRandomIndustryRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 9; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.buildcmd_a = 1; - p->ai.src.direction = 0; - p->ai.src.cargo = fr.cargo | 0x80; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 9; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.direction = 0; - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 3; - p->ai.num_want_fullload = 3; - -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantLongRoadPassengerRoute(Player *p) -{ - int i; - FoundRoute fr; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 55, 180 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 55, 180 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - - if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 10; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.buildcmd_a = 1; - p->ai.src.direction = 0; - p->ai.src.cargo = CT_PASSENGERS; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 10; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.direction = 0; - p->ai.dst.cargo = CT_PASSENGERS; - - // Fill common fields - p->ai.cargo_type = CT_PASSENGERS; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 4; - p->ai.num_want_fullload = 0; - -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantPassengerRouteInsideTown(Player *p) -{ - int i; - FoundRoute fr; - Town *t; - - i = 60; - for (;;) { - // Find a town big enough - t = AiFindRandomTown(); - if (t != NULL && t->population >= 700) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - fr.from = fr.to = t; - - if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; - - // Fill the source field - p->ai.src.spec_tile = t->xy; - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 10; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.buildcmd_a = 1; - p->ai.src.direction = 0; - p->ai.src.cargo = CT_PASSENGERS; - - // Fill the dest field - p->ai.dst.spec_tile = t->xy; - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 10; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.buildcmd_a = 0xFF; - p->ai.dst.direction = 0; - p->ai.dst.cargo = CT_PASSENGERS; - - // Fill common fields - p->ai.cargo_type = CT_PASSENGERS; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 2; - p->ai.num_want_fullload = 0; - -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; - p->ai.state_mode = -1; - p->ai.state_counter = 0; - p->ai.timeout_counter = 0; -} - -static void AiWantRoadRoute(Player *p) -{ - uint16 r = GB(Random(), 0, 16); - - if (r > 0x4000) { - AiWantLongRoadIndustryRoute(p); - } else if (r > 0x2000) { - AiWantMediumRoadIndustryRoute(p); - } else if (r > 0x1000) { - AiWantLongRoadPassengerRoute(p); - } else { - AiWantPassengerRouteInsideTown(p); - } -} - -static void AiWantPassengerAircraftRoute(Player *p) -{ - FoundRoute fr; - int i; - - i = 60; - for (;;) { - // look for one from the subsidy list - AiFindSubsidyPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 0, 95 + 1)) break; - - // try a random one - AiFindRandomPassengerRoute(&fr); - if (IS_INT_INSIDE(fr.distance, 0, 95 + 1)) break; - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - if (!AiCheckIfRouteIsGood(p, &fr, 4)) return; - - - // Fill the source field - p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 12; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.cargo = fr.cargo; - - // Fill the dest field - p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 12; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.cargo = fr.cargo; - - // Fill common fields - p->ai.cargo_type = fr.cargo; - p->ai.build_kind = 0; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 1; - p->ai.num_want_fullload = 1; -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_AIRPORT_STUFF; - p->ai.timeout_counter = 0; -} - -static void AiWantOilRigAircraftRoute(Player *p) -{ - int i; - FoundRoute fr; - Town *t; - Industry *in; - - i = 60; - for (;;) { - // Find a town - t = AiFindRandomTown(); - if (t != NULL) { - // Find a random oil rig industry - in = AiFindRandomIndustry(); - if (in != NULL && in->type == IT_OIL_RIG) { - if (DistanceManhattan(t->xy, in->xy) < 60) - break; - } - } - - // only test 60 times - if (--i == 0) return; - } - - fr.cargo = CT_PASSENGERS; - fr.from = fr.to = t; - - if (!AiCheckIfRouteIsGood(p, &fr, 4)) return; - - // Fill the source field - p->ai.src.spec_tile = t->xy; - p->ai.src.use_tile = 0; - p->ai.src.rand_rng = 12; - p->ai.src.cur_building_rule = 0xFF; - p->ai.src.cargo = CT_PASSENGERS; - - // Fill the dest field - p->ai.dst.spec_tile = in->xy; - p->ai.dst.use_tile = 0; - p->ai.dst.rand_rng = 5; - p->ai.dst.cur_building_rule = 0xFF; - p->ai.dst.cargo = CT_PASSENGERS; - - // Fill common fields - p->ai.cargo_type = CT_PASSENGERS; - p->ai.build_kind = 1; - p->ai.num_build_rec = 2; - p->ai.num_loco_to_build = 1; - p->ai.num_want_fullload = 0; -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.order_list_blocks[0] = 0; - p->ai.order_list_blocks[1] = 1; - p->ai.order_list_blocks[2] = 255; - - p->ai.state = AIS_AIRPORT_STUFF; - p->ai.timeout_counter = 0; -} - -static void AiWantAircraftRoute(Player *p) -{ - uint16 r = (uint16)Random(); - - if (r >= 0x2AAA || _date < 0x3912 + DAYS_TILL_ORIGINAL_BASE_YEAR) { - AiWantPassengerAircraftRoute(p); - } else { - AiWantOilRigAircraftRoute(p); - } -} - -static void AiWantShipRoute(Player *p) -{ - // XXX -// error("AiWaitShipRoute"); -} - - - -static void AiStateWantNewRoute(Player *p) -{ - uint16 r; - int i; - - if (p->player_money < AiGetBasePrice(p) * 500) { - p->ai.state = AIS_0; - return; - } - - i = 200; - for (;;) { - r = (uint16)Random(); - - if (_patches.ai_disable_veh_train && - _patches.ai_disable_veh_roadveh && - _patches.ai_disable_veh_aircraft && - _patches.ai_disable_veh_ship) { - return; - } - - if (r < 0x7626) { - if (_patches.ai_disable_veh_train) continue; - AiWantTrainRoute(p); - } else if (r < 0xC4EA) { - if (_patches.ai_disable_veh_roadveh) continue; - AiWantRoadRoute(p); - } else if (r < 0xD89B) { - if (_patches.ai_disable_veh_aircraft) continue; - AiWantAircraftRoute(p); - } else { - if (_patches.ai_disable_veh_ship) continue; - AiWantShipRoute(p); - } - - // got a route? - if (p->ai.state != AIS_WANT_NEW_ROUTE) break; - - // time out? - if (--i == 0) { - if (++p->ai.state_counter == 556) p->ai.state = AIS_0; - break; - } - } -} - -static bool AiCheckTrackResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint rad = (_patches.modified_catchment) ? CA_TRAIN : 4; - - for (; p->mode != 4; p++) { - AcceptedCargo values; - TileIndex tile2; - uint w; - uint h; - - if (p->mode != 1) continue; - - tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - w = GB(p->attr, 1, 3); - h = GB(p->attr, 4, 3); - - if (p->attr & 1) uintswap(w, h); - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, w, h, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, w, h, rad); - if (!(values[cargo] & ~7)) - return false; - if (cargo != CT_MAIL) - return true; - return !!((values[cargo]>>1) & ~7); - } - } - - return true; -} - -static int32 AiDoBuildDefaultRailTrack(TileIndex tile, const AiDefaultBlockData* p, RailType railtype, byte flag) -{ - int32 ret; - int32 total_cost = 0; - Town *t = NULL; - int rating = 0; - int i,j,k; - - for (;;) { - // This will seldomly overflow for valid reasons. Mask it to be on the safe side. - uint c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); - - _cleared_town = NULL; - - if (p->mode < 2) { - if (p->mode == 0) { - // Depot - ret = DoCommand(c, railtype, p->attr, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_TRAIN_DEPOT); - } else { - // Station - ret = DoCommand(c, (p->attr&1) | (p->attr>>4)<<8 | (p->attr>>1&7)<<16, railtype, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_RAILROAD_STATION); - } - - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - -clear_town_stuff:; - if (_cleared_town != NULL) { - if (t != NULL && t != _cleared_town) - return CMD_ERROR; - t = _cleared_town; - rating += _cleared_town_rating; - } - } else if (p->mode == 2) { - // Rail - if (IsTileType(c, MP_RAILWAY)) return CMD_ERROR; - - j = p->attr; - k = 0; - - // Build the rail - for (i = 0; i != 6; i++, j >>= 1) { - if (j&1) { - k = i; - ret = DoCommand(c, railtype, i, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - } - } - - /* signals too? */ - if (j & 3) { - // Can't build signals on a road. - if (IsTileType(c, MP_STREET)) return CMD_ERROR; - - if (flag & DC_EXEC) { - j = 4 - j; - do { - ret = DoCommand(c, k, 0, flag, CMD_BUILD_SIGNALS); - } while (--j); - } else { - ret = _price.build_signals; - } - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - } - } else if (p->mode == 3) { - //Clear stuff and then build single rail. - if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; - ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret + _price.build_rail; - - if (flag & DC_EXEC) { - DoCommand(c, railtype, p->attr&1, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_SINGLE_RAIL); - } - - goto clear_town_stuff; - } else { - // Unk - break; - } - - p++; - } - - if (!(flag & DC_EXEC)) { - if (t != NULL && rating > t->ratings[_current_player]) { - return CMD_ERROR; - } - } - - return total_cost; -} - -// Returns rule and cost -static int AiBuildDefaultRailTrack(TileIndex tile, byte p0, byte p1, byte p2, byte p3, byte dir, byte cargo, RailType railtype, int32* cost) -{ - int i; - const AiDefaultRailBlock *p; - - for (i = 0; (p = _default_rail_track_data[i]) != NULL; i++) { - if (p->p0 == p0 && p->p1 == p1 && p->p2 == p2 && p->p3 == p3 && - (p->dir == 0xFF || p->dir == dir || ((p->dir - 1) & 3) == dir)) { - *cost = AiDoBuildDefaultRailTrack(tile, p->data, railtype, DC_NO_TOWN_RATING); - if (!CmdFailed(*cost) && AiCheckTrackResources(tile, p->data, cargo)) - return i; - } - } - - return -1; -} - -static const byte _terraform_up_flags[] = { - 14, 13, 12, 11, - 10, 9, 8, 7, - 6, 5, 4, 3, - 2, 1, 0, 1, - 2, 1, 4, 1, - 2, 1, 8, 1, - 2, 1, 4, 2, - 2, 1 -}; - -static const byte _terraform_down_flags[] = { - 1, 2, 3, 4, - 5, 6, 1, 8, - 9, 10, 8, 12, - 4, 2, 0, 0, - 1, 2, 3, 4, - 5, 6, 2, 8, - 9, 10, 1, 12, - 8, 4 -}; - -static void AiDoTerraformLand(TileIndex tile, int dir, int unk, int mode) -{ - PlayerID old_player; - uint32 r; - Slope slope; - uint h; - - old_player = _current_player; - _current_player = OWNER_NONE; - - r = Random(); - - unk &= (int)r; - - do { - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - r >>= 2; - if (r & 2) { - dir++; - if (r & 1) dir -= 2; - } - dir &= 3; - } while (--unk >= 0); - - slope = GetTileSlope(tile, &h); - - if (slope != SLOPE_FLAT) { - if (mode > 0 || (mode == 0 && !(r & 0xC))) { - // Terraform up - DoCommand(tile, _terraform_up_flags[slope - 1], 1, - DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); - } else if (h != 0) { - // Terraform down - DoCommand(tile, _terraform_down_flags[slope - 1], 0, - DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); - } - } - - _current_player = old_player; -} - -static void AiStateBuildDefaultRailBlocks(Player *p) -{ - uint i; - int j; - AiBuildRec *aib; - int rule; - int32 cost; - - // time out? - if (++p->ai.timeout_counter == 1388) { - p->ai.state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // do the following 8 times - for (i = 0; i < 8; i++) { - // check if we can build the default track - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the track can be build there. - rule = AiBuildDefaultRailTrack(aib->use_tile, - p->ai.build_kind, p->ai.num_wagons, - aib->unk6, aib->unk7, - aib->direction, aib->cargo, - p->ai.railtype_to_use, - &cost - ); - - if (rule == -1) { - // cannot build, terraform after a while - if (p->ai.state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); - } - // also try the other terraform direction - if (++p->ai.state_counter >= 1000) { - p->ai.state_counter = 0; - p->ai.state_mode = -p->ai.state_mode; - } - } else if (CheckPlayerHasMoney(cost)) { - int32 r; - // player has money, build it. - aib->cur_building_rule = rule; - - r = AiDoBuildDefaultRailTrack( - aib->use_tile, - _default_rail_track_data[rule]->data, - p->ai.railtype_to_use, - DC_EXEC | DC_NO_TOWN_RATING - ); - assert(!CmdFailed(r)); - } - } while (++aib,--j); - } - - // check if we're done with all of them - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib,--j); - - // yep, all are done. switch state to the rail building state. - p->ai.state = AIS_BUILD_RAIL; - p->ai.state_mode = 255; -} - -static TileIndex AiGetEdgeOfDefaultRailBlock(byte rule, TileIndex tile, byte cmd, int *dir) -{ - const AiDefaultBlockData *p = _default_rail_track_data[rule]->data; - - while (p->mode != 3 || !((--cmd) & 0x80)) p++; - - return tile + ToTileIndexDiff(p->tileoffs) - TileOffsByDiagDir(*dir = p->attr); -} - -typedef struct AiRailPathFindData { - TileIndex tile; - TileIndex tile2; - int count; - bool flag; -} AiRailPathFindData; - -static bool AiEnumFollowTrack(TileIndex tile, AiRailPathFindData *a, int track, uint length, byte *state) -{ - if (a->flag) return true; - - if (length > 20 || tile == a->tile) { - a->flag = true; - return true; - } - - if (DistanceMax(tile, a->tile2) < 4) a->count++; - - return false; -} - -static bool AiDoFollowTrack(const Player* p) -{ - AiRailPathFindData arpfd; - - arpfd.tile = p->ai.start_tile_a; - arpfd.tile2 = p->ai.cur_tile_a; - arpfd.flag = false; - arpfd.count = 0; - FollowTrack(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a), 0x2000 | TRANSPORT_RAIL, p->ai.cur_dir_a^2, - (TPFEnumProc*)AiEnumFollowTrack, NULL, &arpfd); - return arpfd.count > 8; -} - -typedef struct AiRailFinder { - TileIndex final_tile; - byte final_dir; - byte depth; - byte recursive_mode; - byte cur_best_dir; - byte best_dir; - byte cur_best_depth; - byte best_depth; - uint cur_best_dist; - const byte *best_ptr; - uint best_dist; - TileIndex cur_best_tile, best_tile; - TileIndex bridge_end_tile; - Player *player; -} AiRailFinder; - -static const byte _ai_table_15[4][8] = { - {0, 0, 4, 3, 3, 1, 128 + 0, 64}, - {1, 1, 2, 0, 4, 2, 128 + 1, 65}, - {0, 2, 2, 3, 5, 1, 128 + 2, 66}, - {1, 3, 5, 0, 3, 2, 128 + 3, 67} -}; - -static const byte _dir_table_1[] = { 3, 9, 12, 6}; -static const byte _dir_table_2[] = {12, 6, 3, 9}; - - -static bool AiIsTileBanned(const Player* p, TileIndex tile, byte val) -{ - int i; - - for (i = 0; i != p->ai.banned_tile_count; i++) { - if (p->ai.banned_tiles[i] == tile && p->ai.banned_val[i] == val) { - return true; - } - } - return false; -} - -static void AiBanTile(Player* p, TileIndex tile, byte val) -{ - uint i; - - for (i = lengthof(p->ai.banned_tiles) - 1; i != 0; i--) { - p->ai.banned_tiles[i] = p->ai.banned_tiles[i - 1]; - p->ai.banned_val[i] = p->ai.banned_val[i - 1]; - } - - p->ai.banned_tiles[0] = tile; - p->ai.banned_val[0] = val; - - if (p->ai.banned_tile_count != lengthof(p->ai.banned_tiles)) { - p->ai.banned_tile_count++; - } -} - -static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, int dir); - -static bool AiCheckRailPathBetter(AiRailFinder *arf, const byte *p) -{ - bool better = false; - - if (arf->recursive_mode < 1) { - // Mode is 0. This means destination has not been found yet. - // If the found path is shorter than the current one, remember it. - if (arf->cur_best_dist < arf->best_dist) { - arf->best_dir = arf->cur_best_dir; - arf->best_dist = arf->cur_best_dist; - arf->best_ptr = p; - arf->best_tile = arf->cur_best_tile; - better = true; - } - } else if (arf->recursive_mode > 1) { - // Mode is 2. - if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = 0; - arf->best_ptr = p; - arf->best_tile = 0; - better = true; - } - } - arf->recursive_mode = 0; - arf->cur_best_dist = (uint)-1; - arf->cur_best_depth = 0xff; - - return better; -} - -static inline void AiCheckBuildRailBridgeHere(AiRailFinder *arf, TileIndex tile, const byte *p) -{ - Slope tileh; - uint z; - bool flag; - - int dir2 = p[0] & 3; - - tileh = GetTileSlope(tile, &z); - if (tileh == _dir_table_1[dir2] || (tileh == SLOPE_FLAT && z != 0)) { - TileIndex tile_new = tile; - - // Allow bridges directly over bottom tiles - flag = z == 0; - for (;;) { - TileType type; - - if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! - tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); - type = GetTileType(tile_new); - - if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile_new, NULL) != SLOPE_FLAT) { - if (!flag) return; - break; - } - if (type != MP_WATER && type != MP_RAILWAY && type != MP_STREET) return; - flag = true; - } - - // Is building a (rail)bridge possible at this place (type doesn't matter)? - if (CmdFailed(DoCommand(tile_new, tile, 0 | arf->player->ai.railtype_to_use << 8, DC_AUTO, CMD_BUILD_BRIDGE))) { - return; - } - AiBuildRailRecursive(arf, tile_new, dir2); - - // At the bottom depth, check if the new path is better than the old one. - if (arf->depth == 1) { - if (AiCheckRailPathBetter(arf, p)) arf->bridge_end_tile = tile_new; - } - } -} - -static inline void AiCheckBuildRailTunnelHere(AiRailFinder *arf, TileIndex tile, const byte *p) -{ - uint z; - - if (GetTileSlope(tile, &z) == _dir_table_2[p[0] & 3] && z != 0) { - int32 cost = DoCommand(tile, arf->player->ai.railtype_to_use, 0, DC_AUTO, CMD_BUILD_TUNNEL); - - if (!CmdFailed(cost) && cost <= (arf->player->player_money>>4)) { - AiBuildRailRecursive(arf, _build_tunnel_endtile, p[0]&3); - if (arf->depth == 1) AiCheckRailPathBetter(arf, p); - } - } -} - - -static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, int dir) -{ - const byte *p; - - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - // Reached destination? - if (tile == arf->final_tile) { - if (arf->final_dir != (dir^2)) { - if (arf->recursive_mode != 2) arf->recursive_mode = 1; - } else if (arf->recursive_mode != 2) { - arf->recursive_mode = 2; - arf->cur_best_depth = arf->depth; - } else { - if (arf->depth < arf->cur_best_depth) arf->cur_best_depth = arf->depth; - } - return; - } - - // Depth too deep? - if (arf->depth >= 4) { - uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); - - if (dist < arf->cur_best_dist) { - // Store the tile that is closest to the final position. - arf->cur_best_depth = arf->depth; - arf->cur_best_dist = dist; - arf->cur_best_tile = tile; - arf->cur_best_dir = dir; - } - return; - } - - // Increase recursion depth - arf->depth++; - - // Grab pointer to list of stuff that is possible to build - p = _ai_table_15[dir]; - - // Try to build a single rail in all directions. - if (GetTileZ(tile) == 0) { - p += 6; - } else { - do { - // Make sure the tile is not in the list of banned tiles and that a rail can be built here. - if (!AiIsTileBanned(arf->player, tile, p[0]) && - !CmdFailed(DoCommand(tile, arf->player->ai.railtype_to_use, p[0], DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL))) { - AiBuildRailRecursive(arf, tile, p[1]); - } - - // At the bottom depth? - if (arf->depth == 1) AiCheckRailPathBetter(arf, p); - - p += 2; - } while (!(p[0]&0x80)); - } - - AiCheckBuildRailBridgeHere(arf, tile, p); - AiCheckBuildRailTunnelHere(arf, tile, p+1); - - arf->depth--; -} - - -static const byte _dir_table_3[]= {0x25, 0x2A, 0x19, 0x16}; - -static void AiBuildRailConstruct(Player *p) -{ - AiRailFinder arf; - int i; - - // Check too much lookahead? - if (AiDoFollowTrack(p)) { - p->ai.state_counter = (Random()&0xE)+6; // Destruct this amount of blocks - p->ai.state_mode = 1; // Start destruct - - // Ban this tile and don't reach it for a while. - AiBanTile(p, p->ai.cur_tile_a, FindFirstBit(GetRailTrackStatus(p->ai.cur_tile_a))); - return; - } - - // Setup recursive finder and call it. - arf.player = p; - arf.final_tile = p->ai.cur_tile_b; - arf.final_dir = p->ai.cur_dir_b; - arf.depth = 0; - arf.recursive_mode = 0; - arf.best_ptr = NULL; - arf.cur_best_dist = (uint)-1; - arf.cur_best_depth = 0xff; - arf.best_dist = (uint)-1; - arf.best_depth = 0xff; - arf.cur_best_tile = 0; - arf.best_tile = 0; - AiBuildRailRecursive(&arf, p->ai.cur_tile_a, p->ai.cur_dir_a); - - // Reached destination? - if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { - p->ai.state_mode = 255; - return; - } - - // Didn't find anything to build? - if (arf.best_ptr == NULL) { - // Terraform some - for (i = 0; i != 5; i++) { - AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); - } - - if (++p->ai.state_counter == 21) { - p->ai.state_counter = 40; - p->ai.state_mode = 1; - - // Ban this tile - AiBanTile(p, p->ai.cur_tile_a, FindFirstBit(GetRailTrackStatus(p->ai.cur_tile_a))); - } - return; - } - - p->ai.cur_tile_a += TileOffsByDiagDir(p->ai.cur_dir_a); - - if (arf.best_ptr[0] & 0x80) { - int i; - int32 bridge_len = GetBridgeLength(arf.bridge_end_tile, p->ai.cur_tile_a); - - /* Figure out which (rail)bridge type to build - * start with best bridge, then go down to worse and worse bridges - * unnecessary to check for worst bridge (i=0), since AI will always build - * that. AI is so fucked up that fixing this small thing will probably not - * solve a thing - */ - for (i = MAX_BRIDGES - 1; i != 0; i--) { - if (CheckBridge_Stuff(i, bridge_len)) { - int32 cost = DoCommand(arf.bridge_end_tile, p->ai.cur_tile_a, i | (p->ai.railtype_to_use << 8), DC_AUTO, CMD_BUILD_BRIDGE); - if (!CmdFailed(cost) && cost < (p->player_money >> 5)) break; - } - } - - // Build it - DoCommand(arf.bridge_end_tile, p->ai.cur_tile_a, i | (p->ai.railtype_to_use << 8), DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); - - p->ai.cur_tile_a = arf.bridge_end_tile; - p->ai.state_counter = 0; - } else if (arf.best_ptr[0] & 0x40) { - // tunnel - DoCommand(p->ai.cur_tile_a, p->ai.railtype_to_use, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); - p->ai.cur_tile_a = _build_tunnel_endtile; - p->ai.state_counter = 0; - } else { - // rail - p->ai.cur_dir_a = arf.best_ptr[1]; - DoCommand(p->ai.cur_tile_a, p->ai.railtype_to_use, arf.best_ptr[0], - DC_EXEC | DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL); - p->ai.state_counter = 0; - } - - if (arf.best_tile != 0) { - for (i = 0; i != 2; i++) { - AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); - } - } -} - -static bool AiRemoveTileAndGoForward(Player *p) -{ - byte b; - int bit; - const byte *ptr; - TileIndex tile = p->ai.cur_tile_a; - TileIndex tilenew; - - if (IsTileType(tile, MP_TUNNELBRIDGE)) { - if (IsTunnel(tile)) { - // Clear the tunnel and continue at the other side of it. - if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) - return false; - p->ai.cur_tile_a = TILE_MASK(_build_tunnel_endtile - TileOffsByDiagDir(p->ai.cur_dir_a)); - return true; - } else { - // Check if the bridge points in the right direction. - // This is not really needed the first place AiRemoveTileAndGoForward is called. - if (DiagDirToAxis(GetBridgeRampDirection(tile)) != (p->ai.cur_dir_a & 1U)) return false; - - tile = GetOtherBridgeEnd(tile); - - tilenew = TILE_MASK(tile - TileOffsByDiagDir(p->ai.cur_dir_a)); - // And clear the bridge. - if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) - return false; - p->ai.cur_tile_a = tilenew; - return true; - } - } - - // Find the railtype at the position. Quit if no rail there. - b = GetRailTrackStatus(tile) & _dir_table_3[p->ai.cur_dir_a]; - if (b == 0) return false; - - // Convert into a bit position that CMD_REMOVE_SINGLE_RAIL expects. - bit = FindFirstBit(b); - - // Then remove and signals if there are any. - if (IsTileType(tile, MP_RAILWAY) && - GetRailTileType(tile) == RAIL_TILE_SIGNALS) { - DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_SIGNALS); - } - - // And also remove the rail. - if (CmdFailed(DoCommand(tile, 0, bit, DC_EXEC, CMD_REMOVE_SINGLE_RAIL))) - return false; - - // Find the direction at the other edge of the rail. - ptr = _ai_table_15[p->ai.cur_dir_a ^ 2]; - while (ptr[0] != bit) ptr += 2; - p->ai.cur_dir_a = ptr[1] ^ 2; - - // And then also switch tile. - p->ai.cur_tile_a = TILE_MASK(p->ai.cur_tile_a - TileOffsByDiagDir(p->ai.cur_dir_a)); - - return true; -} - - -static void AiBuildRailDestruct(Player *p) -{ - // Decrease timeout. - if (!--p->ai.state_counter) { - p->ai.state_mode = 2; - p->ai.state_counter = 0; - } - - // Don't do anything if the destination is already reached. - if (p->ai.cur_tile_a == p->ai.start_tile_a) return; - - AiRemoveTileAndGoForward(p); -} - - -static void AiBuildRail(Player *p) -{ - switch (p->ai.state_mode) { - case 0: // Construct mode, build new rail. - AiBuildRailConstruct(p); - break; - - case 1: // Destruct mode, destroy the rail currently built. - AiBuildRailDestruct(p); - break; - - case 2: { - uint i; - - // Terraform some and then try building again. - for (i = 0; i != 4; i++) { - AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); - } - - if (++p->ai.state_counter == 4) { - p->ai.state_counter = 0; - p->ai.state_mode = 0; - } - } - - default: break; - } -} - -static void AiStateBuildRail(Player *p) -{ - int num; - AiBuildRec *aib; - byte cmd; - TileIndex tile; - int dir; - - // time out? - if (++p->ai.timeout_counter == 1388) { - p->ai.state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // Currently building a rail between two points? - if (p->ai.state_mode != 255) { - AiBuildRail(p); - - // Alternate between edges - swap_tile(&p->ai.start_tile_a, &p->ai.start_tile_b); - swap_tile(&p->ai.cur_tile_a, &p->ai.cur_tile_b); - swap_byte(&p->ai.start_dir_a, &p->ai.start_dir_b); - swap_byte(&p->ai.cur_dir_a, &p->ai.cur_dir_b); - return; - } - - // Now, find two new points to build between - num = p->ai.num_build_rec; - aib = &p->ai.src; - - for (;;) { - cmd = aib->buildcmd_a; - aib->buildcmd_a = 255; - if (cmd != 255) break; - - cmd = aib->buildcmd_b; - aib->buildcmd_b = 255; - if (cmd != 255) break; - - aib++; - if (--num == 0) { - p->ai.state = AIS_BUILD_RAIL_VEH; - p->ai.state_counter = 0; // timeout - return; - } - } - - // Find first edge to build from. - tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, cmd&3, &dir); - p->ai.start_tile_a = tile; - p->ai.cur_tile_a = tile; - p->ai.start_dir_a = dir; - p->ai.cur_dir_a = dir; - DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir&1)?1:0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); - - assert(TILE_MASK(tile) != 0xFF00); - - // Find second edge to build to - aib = (&p->ai.src) + ((cmd >> 4)&0xF); - tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, (cmd>>2)&3, &dir); - p->ai.start_tile_b = tile; - p->ai.cur_tile_b = tile; - p->ai.start_dir_b = dir; - p->ai.cur_dir_b = dir; - DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir&1)?1:0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); - - assert(TILE_MASK(tile) != 0xFF00); - - // And setup state. - p->ai.state_mode = 2; - p->ai.state_counter = 0; - p->ai.banned_tile_count = 0; -} - -static StationID AiGetStationIdByDef(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _default_rail_track_data[id]->data; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static EngineID AiFindBestWagon(CargoID cargo, RailType railtype) -{ - EngineID best_veh_index = INVALID_ENGINE; - EngineID i; - uint16 best_capacity = 0; - uint16 best_speed = 0; - uint speed; - - for (i = 0; i < NUM_TRAIN_ENGINES; i++) { - const RailVehicleInfo *rvi = RailVehInfo(i); - const Engine* e = GetEngine(i); - - if (!IsCompatibleRail(e->railtype, railtype) || - !(rvi->flags & RVI_WAGON) || - !HASBIT(e->player_avail, _current_player)) { - continue; - } - - if (rvi->cargo_type != cargo) continue; - - /* max_speed of 0 indicates no speed limit */ - speed = rvi->max_speed == 0 ? 0xFFFF : rvi->max_speed; - - if (rvi->capacity >= best_capacity && speed >= best_speed) { - best_capacity = rvi->capacity; - best_speed = best_speed; - best_veh_index = i; - } - } - - return best_veh_index; -} - -static void AiStateBuildRailVeh(Player *p) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - EngineID veh; - int i; - CargoID cargo; - int32 cost; - Vehicle *v; - VehicleID loco_id; - - ptr = _default_rail_track_data[p->ai.src.cur_building_rule]->data; - while (ptr->mode != 0) ptr++; - - tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - - cargo = p->ai.cargo_type; - for (i = 0;;) { - if (p->ai.wagon_list[i] == INVALID_VEHICLE) { - veh = AiFindBestWagon(cargo, p->ai.railtype_to_use); - /* veh will return INVALID_ENGINE if no suitable wagon is available. - * We shall treat this in the same way as having no money */ - if (veh == INVALID_ENGINE) goto handle_nocash; - cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); - if (CmdFailed(cost)) goto handle_nocash; - p->ai.wagon_list[i] = _new_vehicle_id; - p->ai.wagon_list[i + 1] = INVALID_VEHICLE; - return; - } - if (cargo == CT_MAIL) cargo = CT_PASSENGERS; - if (++i == p->ai.num_wagons * 2 - 1) break; - } - - // Which locomotive to build? - veh = AiChooseTrainToBuild(p->ai.railtype_to_use, p->player_money, cargo != CT_PASSENGERS ? 1 : 0, tile); - if (veh == INVALID_ENGINE) { -handle_nocash: - // after a while, if AI still doesn't have cash, get out of this block by selling the wagons. - if (++p->ai.state_counter == 1000) { - for (i = 0; p->ai.wagon_list[i] != INVALID_VEHICLE; i++) { - cost = DoCommand(tile, p->ai.wagon_list[i], 0, DC_EXEC, CMD_SELL_RAIL_WAGON); - assert(!CmdFailed(cost)); - } - p->ai.state = AIS_0; - } - return; - } - - // Try to build the locomotive - cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); - assert(!CmdFailed(cost)); - loco_id = _new_vehicle_id; - - // Sell a vehicle if the train is double headed. - v = GetVehicle(loco_id); - if (v->next != NULL) { - i = p->ai.wagon_list[p->ai.num_wagons * 2 - 2]; - p->ai.wagon_list[p->ai.num_wagons * 2 - 2] = INVALID_VEHICLE; - DoCommand(tile, i, 0, DC_EXEC, CMD_SELL_RAIL_WAGON); - } - - // Move the wagons onto the train - for (i = 0; p->ai.wagon_list[i] != INVALID_VEHICLE; i++) { - DoCommand(tile, p->ai.wagon_list[i] | (loco_id << 16), 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); - } - - for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { - const AiBuildRec* aib = &p->ai.src + p->ai.order_list_blocks[i]; - bool is_pass = ( - p->ai.cargo_type == CT_PASSENGERS || - p->ai.cargo_type == CT_MAIL || - (_opt.landscape == LT_NORMAL && p->ai.cargo_type == CT_VALUABLES) - ); - Order order; - - order.type = OT_GOTO_STATION; - order.flags = 0; - order.dest = AiGetStationIdByDef(aib->use_tile, aib->cur_building_rule); - - if (!is_pass && i == 1) order.flags |= OF_UNLOAD; - if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) - order.flags |= OF_FULL_LOAD; - - DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_TRAIN); - - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; - - if (--p->ai.num_loco_to_build != 0) { -// p->ai.loco_id = INVALID_VEHICLE; - p->ai.wagon_list[0] = INVALID_VEHICLE; - } else { - p->ai.state = AIS_0; - } -} - -static void AiStateDeleteRailBlocks(Player *p) -{ - const AiBuildRec* aib = &p->ai.src; - uint num = p->ai.num_build_rec; - - do { - const AiDefaultBlockData* b; - - if (aib->cur_building_rule == 255) continue; - for (b = _default_rail_track_data[aib->cur_building_rule]->data; b->mode != 4; b++) { - DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } while (++aib,--num); - - p->ai.state = AIS_0; -} - -static bool AiCheckRoadResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint values[NUM_CARGO]; - int rad; - - if (_patches.modified_catchment) { - rad = CA_TRUCK; // Same as CA_BUS at the moment? - } else { // change that at some point? - rad = 4; - } - - for (;; p++) { - if (p->mode == 4) { - return true; - } else if (p->mode == 1) { - TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, 1, 1, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, 1, 1, rad); - return (values[cargo]&~7) != 0; - } - } - } -} - -static bool _want_road_truck_station; -static int32 AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag); - -// Returns rule and cost -static int AiFindBestDefaultRoadBlock(TileIndex tile, byte direction, byte cargo, int32 *cost) -{ - int i; - const AiDefaultRoadBlock *p; - - _want_road_truck_station = (cargo & 0x7F) != CT_PASSENGERS; - - for (i = 0; (p = _road_default_block_data[i]) != NULL; i++) { - if (p->dir == direction) { - *cost = AiDoBuildDefaultRoadBlock(tile, p->data, 0); - if (!CmdFailed(*cost) && AiCheckRoadResources(tile, p->data, cargo)) - return i; - } - } - - return -1; -} - -static int32 AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag) -{ - int32 ret; - int32 total_cost = 0; - Town *t = NULL; - int rating = 0; - int roadflag = 0; - - for (;p->mode != 4;p++) { - TileIndex c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); - - _cleared_town = NULL; - - if (p->mode == 2) { - if (IsTileType(c, MP_STREET) && - GetRoadTileType(c) == ROAD_TILE_NORMAL && - (GetRoadBits(c) & p->attr) != 0) { - roadflag |= 2; - - // all bits are already built? - if ((GetRoadBits(c) & p->attr) == p->attr) continue; - } - - ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - - continue; - } - - if (p->mode == 0) { - // Depot - ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_DEPOT); - goto clear_town_stuff; - } else if (p->mode == 1) { - if (_want_road_truck_station) { - // Truck station - ret = DoCommand(c, p->attr, RS_TRUCK, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); - } else { - // Bus station - ret = DoCommand(c, p->attr, RS_BUS, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); - } -clear_town_stuff:; - - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - - if (_cleared_town != NULL) { - if (t != NULL && t != _cleared_town) return CMD_ERROR; - t = _cleared_town; - rating += _cleared_town_rating; - } - } else if (p->mode == 3) { - if (flag & DC_EXEC) continue; - - if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; - - if (!IsTileType(c, MP_STREET) || GetRoadTileType(c) != ROAD_TILE_NORMAL) { - ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(ret)) return CMD_ERROR; - } - - } - } - - if (!_want_road_truck_station && !(roadflag & 2)) return CMD_ERROR; - - if (!(flag & DC_EXEC)) { - if (t != NULL && rating > t->ratings[_current_player]) return CMD_ERROR; - } - return total_cost; -} - -// Make sure the blocks are not too close to each other -static bool AiCheckBlockDistances(Player *p, TileIndex tile) -{ - const AiBuildRec* aib = &p->ai.src; - uint num = p->ai.num_build_rec; - - do { - if (aib->cur_building_rule != 255) { - if (DistanceManhattan(aib->use_tile, tile) < 9) return false; - } - } while (++aib, --num); - - return true; -} - - -static void AiStateBuildDefaultRoadBlocks(Player *p) -{ - uint i; - int j; - AiBuildRec *aib; - int rule; - int32 cost; - - // time out? - if (++p->ai.timeout_counter == 1388) { - p->ai.state = AIS_DELETE_RAIL_BLOCKS; - return; - } - - // do the following 8 times - for (i = 0; i != 8; i++) { - // check if we can build the default track - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the road can be built there. - rule = AiFindBestDefaultRoadBlock( - aib->use_tile, aib->direction, aib->cargo, &cost - ); - - if (rule == -1) { - // cannot build, terraform after a while - if (p->ai.state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); - } - // also try the other terraform direction - if (++p->ai.state_counter >= 1000) { - p->ai.state_counter = 0; - p->ai.state_mode = -p->ai.state_mode; - } - } else if (CheckPlayerHasMoney(cost) && AiCheckBlockDistances(p,aib->use_tile)) { - int32 r; - - // player has money, build it. - aib->cur_building_rule = rule; - - r = AiDoBuildDefaultRoadBlock( - aib->use_tile, - _road_default_block_data[rule]->data, - DC_EXEC | DC_NO_TOWN_RATING - ); - assert(!CmdFailed(r)); - } - } while (++aib,--j); - } - - // check if we're done with all of them - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib,--j); - - // yep, all are done. switch state to the rail building state. - p->ai.state = AIS_BUILD_ROAD; - p->ai.state_mode = 255; -} - -typedef struct { - TileIndex final_tile; - byte final_dir; - byte depth; - byte recursive_mode; - byte cur_best_dir; - byte best_dir; - byte cur_best_depth; - byte best_depth; - uint cur_best_dist; - const byte *best_ptr; - uint best_dist; - TileIndex cur_best_tile, best_tile; - TileIndex bridge_end_tile; - Player *player; -} AiRoadFinder; - -typedef struct AiRoadEnum { - TileIndex dest; - TileIndex best_tile; - int best_track; - uint best_dist; -} AiRoadEnum; - -static const byte _dir_by_track[] = { - 0, 1, 0, 1, 2, 1, - 0, 0, - 2, 3, 3, 2, 3, 0, -}; - -static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, int dir); - -static bool AiCheckRoadPathBetter(AiRoadFinder *arf, const byte *p) -{ - bool better = false; - - if (arf->recursive_mode < 1) { - // Mode is 0. This means destination has not been found yet. - // If the found path is shorter than the current one, remember it. - if (arf->cur_best_dist < arf->best_dist || - (arf->cur_best_dist == arf->best_dist && arf->cur_best_depth < arf->best_depth)) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = arf->cur_best_dist; - arf->best_dir = arf->cur_best_dir; - arf->best_ptr = p; - arf->best_tile = arf->cur_best_tile; - better = true; - } - } else if (arf->recursive_mode > 1) { - // Mode is 2. - if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { - arf->best_depth = arf->cur_best_depth; - arf->best_dist = 0; - arf->best_ptr = p; - arf->best_tile = 0; - better = true; - } - } - arf->recursive_mode = 0; - arf->cur_best_dist = (uint)-1; - arf->cur_best_depth = 0xff; - - return better; -} - - -static bool AiEnumFollowRoad(TileIndex tile, AiRoadEnum *a, int track, uint length, byte *state) -{ - uint dist = DistanceManhattan(tile, a->dest); - - if (dist <= a->best_dist) { - TileIndex tile2 = TILE_MASK(tile + TileOffsByDiagDir(_dir_by_track[track])); - - if (IsTileType(tile2, MP_STREET) && GetRoadTileType(tile2) == ROAD_TILE_NORMAL) { - a->best_dist = dist; - a->best_tile = tile; - a->best_track = track; - } - } - - return false; -} - -static const uint16 _ai_road_table_and[4] = { - 0x1009, - 0x16, - 0x520, - 0x2A00, -}; - -static bool AiCheckRoadFinished(Player *p) -{ - AiRoadEnum are; - TileIndex tile; - int dir = p->ai.cur_dir_a; - uint32 bits; - int i; - - are.dest = p->ai.cur_tile_b; - tile = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(dir)); - - if (IsRoadStopTile(tile) || IsTileDepotType(tile, TRANSPORT_ROAD)) return false; - bits = GetTileTrackStatus(tile, TRANSPORT_ROAD) & _ai_road_table_and[dir]; - if (bits == 0) return false; - - are.best_dist = (uint)-1; - - for_each_bit(i, bits) { - FollowTrack(tile, 0x3000 | TRANSPORT_ROAD, _dir_by_track[i], (TPFEnumProc*)AiEnumFollowRoad, NULL, &are); - } - - if (DistanceManhattan(tile, are.dest) <= are.best_dist) return false; - - if (are.best_dist == 0) return true; - - p->ai.cur_tile_a = are.best_tile; - p->ai.cur_dir_a = _dir_by_track[are.best_track]; - return false; -} - - -static bool AiBuildRoadHelper(TileIndex tile, int flags, int type) -{ - static const RoadBits _road_bits[] = { - ROAD_X, - ROAD_Y, - ROAD_NW | ROAD_NE, - ROAD_SW | ROAD_SE, - ROAD_NW | ROAD_SW, - ROAD_SE | ROAD_NE - }; - return !CmdFailed(DoCommand(tile, _road_bits[type], 0, flags, CMD_BUILD_ROAD)); -} - -static inline void AiCheckBuildRoadBridgeHere(AiRoadFinder *arf, TileIndex tile, const byte *p) -{ - Slope tileh; - uint z; - bool flag; - - int dir2 = p[0] & 3; - - tileh = GetTileSlope(tile, &z); - if (tileh == _dir_table_1[dir2] || (tileh == SLOPE_FLAT && z != 0)) { - TileIndex tile_new = tile; - - // Allow bridges directly over bottom tiles - flag = z == 0; - for (;;) { - TileType type; - - if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! - tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); - type = GetTileType(tile_new); - - if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile, NULL) != SLOPE_FLAT) { - // Allow a bridge if either we have a tile that's water, rail or street, - // or if we found an up tile. - if (!flag) return; - break; - } - if (type != MP_WATER && type != MP_RAILWAY && type != MP_STREET) return; - flag = true; - } - - // Is building a (rail)bridge possible at this place (type doesn't matter)? - if (CmdFailed(DoCommand(tile_new, tile, 0x8000, DC_AUTO, CMD_BUILD_BRIDGE))) - return; - AiBuildRoadRecursive(arf, tile_new, dir2); - - // At the bottom depth, check if the new path is better than the old one. - if (arf->depth == 1) { - if (AiCheckRoadPathBetter(arf, p)) arf->bridge_end_tile = tile_new; - } - } -} - -static inline void AiCheckBuildRoadTunnelHere(AiRoadFinder *arf, TileIndex tile, const byte *p) -{ - uint z; - - if (GetTileSlope(tile, &z) == _dir_table_2[p[0] & 3] && z != 0) { - int32 cost = DoCommand(tile, 0x200, 0, DC_AUTO, CMD_BUILD_TUNNEL); - - if (!CmdFailed(cost) && cost <= (arf->player->player_money>>4)) { - AiBuildRoadRecursive(arf, _build_tunnel_endtile, p[0]&3); - if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); - } - } -} - - - -static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, int dir) -{ - const byte *p; - - tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); - - // Reached destination? - if (tile == arf->final_tile) { - if ((arf->final_dir^2) == dir) { - arf->recursive_mode = 2; - arf->cur_best_depth = arf->depth; - } - return; - } - - // Depth too deep? - if (arf->depth >= 4) { - uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); - if (dist < arf->cur_best_dist) { - // Store the tile that is closest to the final position. - arf->cur_best_dist = dist; - arf->cur_best_tile = tile; - arf->cur_best_dir = dir; - arf->cur_best_depth = arf->depth; - } - return; - } - - // Increase recursion depth - arf->depth++; - - // Grab pointer to list of stuff that is possible to build - p = _ai_table_15[dir]; - - // Try to build a single rail in all directions. - if (GetTileZ(tile) == 0) { - p += 6; - } else { - do { - // Make sure that a road can be built here. - if (AiBuildRoadHelper(tile, DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, p[0])) { - AiBuildRoadRecursive(arf, tile, p[1]); - } - - // At the bottom depth? - if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); - - p += 2; - } while (!(p[0] & 0x80)); - } - - AiCheckBuildRoadBridgeHere(arf, tile, p); - AiCheckBuildRoadTunnelHere(arf, tile, p+1); - - arf->depth--; -} - - -static void AiBuildRoadConstruct(Player *p) -{ - AiRoadFinder arf; - int i; - TileIndex tile; - - // Reached destination? - if (AiCheckRoadFinished(p)) { - p->ai.state_mode = 255; - return; - } - - // Setup recursive finder and call it. - arf.player = p; - arf.final_tile = p->ai.cur_tile_b; - arf.final_dir = p->ai.cur_dir_b; - arf.depth = 0; - arf.recursive_mode = 0; - arf.best_ptr = NULL; - arf.cur_best_dist = (uint)-1; - arf.cur_best_depth = 0xff; - arf.best_dist = (uint)-1; - arf.best_depth = 0xff; - arf.cur_best_tile = 0; - arf.best_tile = 0; - AiBuildRoadRecursive(&arf, p->ai.cur_tile_a, p->ai.cur_dir_a); - - // Reached destination? - if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { - p->ai.state_mode = 255; - return; - } - - // Didn't find anything to build? - if (arf.best_ptr == NULL) { - // Terraform some -do_some_terraform: - for (i = 0; i != 5; i++) - AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); - - if (++p->ai.state_counter == 21) { - p->ai.state_mode = 1; - - p->ai.cur_tile_a = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a)); - p->ai.cur_dir_a ^= 2; - p->ai.state_counter = 0; - } - return; - } - - tile = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a)); - - if (arf.best_ptr[0]&0x80) { - int i; - int32 bridge_len; - p->ai.cur_tile_a = arf.bridge_end_tile; - bridge_len = GetBridgeLength(tile, p->ai.cur_tile_a); // tile - - /* Figure out what (road)bridge type to build - * start with best bridge, then go down to worse and worse bridges - * unnecessary to check for worse bridge (i=0), since AI will always build that. - *AI is so fucked up that fixing this small thing will probably not solve a thing - */ - for (i = 10; i != 0; i--) { - if (CheckBridge_Stuff(i, bridge_len)) { - int32 cost = DoCommand(tile, p->ai.cur_tile_a, i + (0x80 << 8), DC_AUTO, CMD_BUILD_BRIDGE); - if (!CmdFailed(cost) && cost < (p->player_money >> 5)) break; - } - } - - // Build it - DoCommand(tile, p->ai.cur_tile_a, i + (0x80 << 8), DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); - - p->ai.state_counter = 0; - } else if (arf.best_ptr[0]&0x40) { - // tunnel - DoCommand(tile, 0x200, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); - p->ai.cur_tile_a = _build_tunnel_endtile; - p->ai.state_counter = 0; - } else { - // road - if (!AiBuildRoadHelper(tile, DC_EXEC | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, arf.best_ptr[0])) - goto do_some_terraform; - - p->ai.cur_dir_a = arf.best_ptr[1]; - p->ai.cur_tile_a = tile; - p->ai.state_counter = 0; - } - - if (arf.best_tile != 0) { - for (i = 0; i != 2; i++) - AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); - } -} - - -static void AiBuildRoad(Player *p) -{ - if (p->ai.state_mode < 1) { - // Construct mode, build new road. - AiBuildRoadConstruct(p); - } else if (p->ai.state_mode == 1) { - // Destruct mode, not implemented for roads. - p->ai.state_mode = 2; - p->ai.state_counter = 0; - } else if (p->ai.state_mode == 2) { - uint i; - - // Terraform some and then try building again. - for (i = 0; i != 4; i++) { - AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); - } - - if (++p->ai.state_counter == 4) { - p->ai.state_counter = 0; - p->ai.state_mode = 0; - } - } -} - -static TileIndex AiGetRoadBlockEdge(byte rule, TileIndex tile, int *dir) -{ - const AiDefaultBlockData *p = _road_default_block_data[rule]->data; - while (p->mode != 1) p++; - *dir = p->attr; - return TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); -} - - -static void AiStateBuildRoad(Player *p) -{ - int num; - AiBuildRec *aib; - byte cmd; - TileIndex tile; - int dir; - - // time out? - if (++p->ai.timeout_counter == 1388) { - p->ai.state = AIS_DELETE_ROAD_BLOCKS; - return; - } - - // Currently building a road between two points? - if (p->ai.state_mode != 255) { - AiBuildRoad(p); - - // Alternate between edges - swap_tile(&p->ai.start_tile_a, &p->ai.start_tile_b); - swap_tile(&p->ai.cur_tile_a, &p->ai.cur_tile_b); - swap_byte(&p->ai.start_dir_a, &p->ai.start_dir_b); - swap_byte(&p->ai.cur_dir_a, &p->ai.cur_dir_b); - - return; - } - - // Now, find two new points to build between - num = p->ai.num_build_rec; - aib = &p->ai.src; - - for (;;) { - cmd = aib->buildcmd_a; - aib->buildcmd_a = 255; - if (cmd != 255) break; - - aib++; - if (--num == 0) { - p->ai.state = AIS_BUILD_ROAD_VEHICLES; - return; - } - } - - // Find first edge to build from. - tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); - p->ai.start_tile_a = tile; - p->ai.cur_tile_a = tile; - p->ai.start_dir_a = dir; - p->ai.cur_dir_a = dir; - - // Find second edge to build to - aib = (&p->ai.src) + (cmd&0xF); - tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); - p->ai.start_tile_b = tile; - p->ai.cur_tile_b = tile; - p->ai.start_dir_b = dir; - p->ai.cur_dir_b = dir; - - // And setup state. - p->ai.state_mode = 2; - p->ai.state_counter = 0; - p->ai.banned_tile_count = 0; -} - -static StationID AiGetStationIdFromRoadBlock(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _road_default_block_data[id]->data; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static void AiStateBuildRoadVehicles(Player *p) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - VehicleID loco_id; - EngineID veh; - uint i; - - ptr = _road_default_block_data[p->ai.src.cur_building_rule]->data; - for (; ptr->mode != 0; ptr++) {} - tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - veh = AiChooseRoadVehToBuild(p->ai.cargo_type, p->player_money, tile); - if (veh == INVALID_ENGINE) { - p->ai.state = AIS_0; - return; - } - - if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) return; - - loco_id = _new_vehicle_id; - - if (GetVehicle(loco_id)->cargo_type != p->ai.cargo_type) { - /* Cargo type doesn't match, so refit it */ - if (CmdFailed(DoCommand(tile, loco_id, p->ai.cargo_type, DC_EXEC, CMD_REFIT_ROAD_VEH))) { - /* Refit failed... sell the vehicle */ - DoCommand(tile, loco_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - return; - } - } - - for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { - const AiBuildRec* aib = &p->ai.src + p->ai.order_list_blocks[i]; - bool is_pass = ( - p->ai.cargo_type == CT_PASSENGERS || - p->ai.cargo_type == CT_MAIL || - (_opt.landscape == LT_NORMAL && p->ai.cargo_type == CT_VALUABLES) - ); - Order order; - - order.type = OT_GOTO_STATION; - order.flags = 0; - order.dest = AiGetStationIdFromRoadBlock(aib->use_tile, aib->cur_building_rule); - - if (!is_pass && i == 1) order.flags |= OF_UNLOAD; - if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) - order.flags |= OF_FULL_LOAD; - - DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_ROADVEH); - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; - if (--p->ai.num_loco_to_build == 0) p->ai.state = AIS_0; -} - -static void AiStateDeleteRoadBlocks(Player *p) -{ - const AiBuildRec* aib = &p->ai.src; - uint num = p->ai.num_build_rec; - - do { - const AiDefaultBlockData* b; - - if (aib->cur_building_rule == 255) continue; - for (b = _road_default_block_data[aib->cur_building_rule]->data; b->mode != 4; b++) { - if (b->mode > 1) continue; - DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } while (++aib,--num); - - p->ai.state = AIS_0; -} - - -static void AiStateAirportStuff(Player *p) -{ - const Station* st; - byte acc_planes; - int i; - AiBuildRec *aib; - byte rule; - - // Here we look for an airport we could use instead of building a new - // one. If we find such an aiport for any waypoint, - // AiStateBuildDefaultAirportBlocks() will kindly skip that one when - // building the waypoints. - - i = 0; - do { - // We do this all twice - once for the source (town in the case - // of oilrig route) and then for the destination (oilrig in the - // case of oilrig route). - aib = &p->ai.src + i; - - FOR_ALL_STATIONS(st) { - // Is this an airport? - if (!(st->facilities & FACIL_AIRPORT)) continue; - - // Do we own the airport? (Oilrigs aren't owned, though.) - if (st->owner != OWNER_NONE && st->owner != _current_player) continue; - - acc_planes = GetAirport(st->airport_type)->acc_planes; - - // Dismiss heliports, unless we are checking an oilrig. - if (acc_planes == HELICOPTERS_ONLY && (p->ai.build_kind != 1 || i != 1)) - continue; - - // Dismiss country airports if we are doing the other - // endpoint of an oilrig route. - if (acc_planes == AIRCRAFT_ONLY && (p->ai.build_kind == 1 && i == 0)) - continue; - - // Dismiss airports too far away. - if (DistanceMax(st->airport_tile, aib->spec_tile) > aib->rand_rng) - continue; - - // It's ideal airport, let's take it! - - /* XXX: This part is utterly broken - rule should - * contain number of the rule appropriate for the - * airport type (country, town, ...), see - * _airport_default_block_data (rule is just an index - * in this array). But the only difference between the - * currently existing two rules (rule 0 - town and rule - * 1 - country) is the attr field which is used only - * when building new airports - and that's irrelevant - * for us. So using just about any rule will suffice - * here for now (some of the new airport types would be - * broken because they will probably need different - * tileoff values etc), no matter that - * IsHangarTile() makes no sense. --pasky */ - if (acc_planes == HELICOPTERS_ONLY) { - /* Heliports should have maybe own rulesets but - * OTOH we don't want AI to pick them up when - * looking for a suitable airport type to build. - * So any of rules 0 or 1 would do for now. The - * original rule number was 2 but that's a bug - * because we have no such rule. */ - rule = 1; - } else { - rule = IsHangarTile(st->airport_tile); - } - - aib->cur_building_rule = rule; - aib->use_tile = st->airport_tile; - break; - } - } while (++i != p->ai.num_build_rec); - - p->ai.state = AIS_BUILD_DEFAULT_AIRPORT_BLOCKS; - p->ai.state_mode = 255; - p->ai.state_counter = 0; -} - -static int32 AiDoBuildDefaultAirportBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag) -{ - int32 total_cost = 0, ret; - - for (; p->mode == 0; p++) { - if (!HASBIT(_avail_aircraft, p->attr)) return CMD_ERROR; - ret = DoCommand(TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)), p->attr,0,flag | DC_AUTO | DC_NO_WATER,CMD_BUILD_AIRPORT); - if (CmdFailed(ret)) return CMD_ERROR; - total_cost += ret; - } - - return total_cost; -} - -static bool AiCheckAirportResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) -{ - uint values[NUM_CARGO]; - int rad; - - if (_patches.modified_catchment) { - rad = CA_AIR_LARGE; // I Have NFI what airport the - } else { // AI is going to build here - rad = 4; - } - - for (; p->mode == 0; p++) { - TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); - const AirportFTAClass* airport = GetAirport(p->attr); - uint w = airport->size_x; - uint h = airport->size_y; - - if (cargo & 0x80) { - GetProductionAroundTiles(values, tile2, w, h, rad); - return values[cargo & 0x7F] != 0; - } else { - GetAcceptanceAroundTiles(values, tile2, w, h, rad); - return values[cargo] >= 8; - } - } - return true; -} - -static int AiFindBestDefaultAirportBlock(TileIndex tile, byte cargo, byte heli, int32 *cost) -{ - const AiDefaultBlockData *p; - uint i; - - for (i = 0; (p = _airport_default_block_data[i]) != NULL; i++) { - // If we are doing a helicopter service, avoid building - // airports where they can't land. - if (heli && GetAirport(p->attr)->acc_planes == AIRCRAFT_ONLY) continue; - - *cost = AiDoBuildDefaultAirportBlock(tile, p, 0); - if (!CmdFailed(*cost) && AiCheckAirportResources(tile, p, cargo)) - return i; - } - return -1; -} - -static void AiStateBuildDefaultAirportBlocks(Player *p) -{ - int i, j; - AiBuildRec *aib; - int rule; - int32 cost; - - // time out? - if (++p->ai.timeout_counter == 1388) { - p->ai.state = AIS_0; - return; - } - - // do the following 8 times - i = 8; - do { - // check if we can build the default - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - // this item has already been built? - if (aib->cur_building_rule != 255) continue; - - // adjust the coordinate randomly, - // to make sure that we find a position. - aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); - - // check if the aircraft stuff can be built there. - rule = AiFindBestDefaultAirportBlock(aib->use_tile, aib->cargo, p->ai.build_kind, &cost); - -// SetRedErrorSquare(aib->use_tile); - - if (rule == -1) { - // cannot build, terraform after a while - if (p->ai.state_counter >= 600) { - AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); - } - // also try the other terraform direction - if (++p->ai.state_counter >= 1000) { - p->ai.state_counter = 0; - p->ai.state_mode = -p->ai.state_mode; - } - } else if (CheckPlayerHasMoney(cost) && AiCheckBlockDistances(p,aib->use_tile)) { - // player has money, build it. - int32 r; - - aib->cur_building_rule = rule; - - r = AiDoBuildDefaultAirportBlock( - aib->use_tile, - _airport_default_block_data[rule], - DC_EXEC | DC_NO_TOWN_RATING - ); - assert(!CmdFailed(r)); - } - } while (++aib,--j); - } while (--i); - - // check if we're done with all of them - aib = &p->ai.src; - j = p->ai.num_build_rec; - do { - if (aib->cur_building_rule == 255) return; - } while (++aib,--j); - - // yep, all are done. switch state. - p->ai.state = AIS_BUILD_AIRCRAFT_VEHICLES; -} - -static StationID AiGetStationIdFromAircraftBlock(TileIndex tile, int id) -{ - const AiDefaultBlockData *p = _airport_default_block_data[id]; - while (p->mode != 1) p++; - return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); -} - -static void AiStateBuildAircraftVehicles(Player *p) -{ - const AiDefaultBlockData *ptr; - TileIndex tile; - EngineID veh; - int i; - VehicleID loco_id; - - ptr = _airport_default_block_data[p->ai.src.cur_building_rule]; - for (; ptr->mode != 0; ptr++) {} - - tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); - - veh = AiChooseAircraftToBuild(p->player_money, p->ai.build_kind != 0 ? 0 : AIR_CTOL); - if (veh == INVALID_ENGINE) return; - - /* XXX - Have the AI pick the hangar terminal in an airport. Eg get airport-type - * and offset to the FIRST depot because the AI picks the st->xy tile */ - tile += ToTileIndexDiff(GetAirport(GetStationByTile(tile)->airport_type)->airport_depots[0]); - if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) return; - loco_id = _new_vehicle_id; - - for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { - AiBuildRec *aib = (&p->ai.src) + p->ai.order_list_blocks[i]; - bool is_pass = (p->ai.cargo_type == CT_PASSENGERS || p->ai.cargo_type == CT_MAIL); - Order order; - - order.type = OT_GOTO_STATION; - order.flags = 0; - order.dest = AiGetStationIdFromAircraftBlock(aib->use_tile, aib->cur_building_rule); - - if (!is_pass && i == 1) order.flags |= OF_UNLOAD; - if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) - order.flags |= OF_FULL_LOAD; - - DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - } - - DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_AIRCRAFT); - - DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); - - if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; - - if (--p->ai.num_loco_to_build == 0) p->ai.state = AIS_0; -} - -static void AiStateCheckShipStuff(Player *p) -{ - // XXX - error("!AiStateCheckShipStuff"); -} - -static void AiStateBuildDefaultShipBlocks(Player *p) -{ - // XXX - error("!AiStateBuildDefaultShipBlocks"); -} - -static void AiStateDoShipStuff(Player *p) -{ - // XXX - error("!AiStateDoShipStuff"); -} - -static void AiStateSellVeh(Player *p) -{ - Vehicle *v = p->ai.cur_veh; - - if (v->owner == _current_player) { - if (v->type == VEH_Train) { - - if (!IsTileDepotType(v->tile, TRANSPORT_RAIL) || v->u.rail.track != 0x80 || !(v->vehstatus&VS_STOPPED)) { - if (v->current_order.type != OT_GOTO_DEPOT) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_TRAIN_TO_DEPOT); - goto going_to_depot; - } - - // Sell whole train - DoCommand(v->tile, v->index, 1, DC_EXEC, CMD_SELL_RAIL_WAGON); - - } else if (v->type == VEH_Road) { - if (!IsRoadVehInDepotStopped(v)) { - if (v->current_order.type != OT_GOTO_DEPOT) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); - goto going_to_depot; - } - - DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - } else if (v->type == VEH_Aircraft) { - if (!IsAircraftInHangarStopped(v)) { - if (v->current_order.type != OT_GOTO_DEPOT) - DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_AIRCRAFT_TO_HANGAR); - goto going_to_depot; - } - - DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT); - } else if (v->type == VEH_Ship) { - // XXX: not implemented - error("!v->type == VEH_Ship"); - } - } - - goto return_to_loop; -going_to_depot:; - if (++p->ai.state_counter <= 832) return; - - if (v->current_order.type == OT_GOTO_DEPOT) { - v->current_order.type = OT_DUMMY; - v->current_order.flags = 0; - InvalidateWindow(WC_VEHICLE_VIEW, v->index); - } -return_to_loop:; - p->ai.state = AIS_VEH_LOOP; -} - -static void AiStateRemoveStation(Player *p) -{ - // Remove stations that aren't in use by any vehicle - byte *in_use; - const Order *ord; - const Station *st; - TileIndex tile; - - // Go to this state when we're done. - p->ai.state = AIS_1; - - // Get a list of all stations that are in use by a vehicle - in_use = malloc(GetMaxStationIndex() + 1); - memset(in_use, 0, GetMaxStationIndex() + 1); - FOR_ALL_ORDERS(ord) { - if (ord->type == OT_GOTO_STATION) in_use[ord->dest] = 1; - } - - // Go through all stations and delete those that aren't in use - FOR_ALL_STATIONS(st) { - if (st->owner == _current_player && !in_use[st->index] && - ( (st->bus_stops != NULL && (tile = st->bus_stops->xy) != 0) || - (st->truck_stops != NULL && (tile = st->truck_stops->xy)) != 0 || - (tile = st->train_tile) != 0 || - (tile = st->dock_tile) != 0 || - (tile = st->airport_tile) != 0)) { - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } - - free(in_use); -} - -static void AiRemovePlayerRailOrRoad(Player *p, TileIndex tile) -{ - TrackBits rails; - - if (IsTileType(tile, MP_RAILWAY)) { - if (!IsTileOwner(tile, _current_player)) return; - - if (IsPlainRailTile(tile)) { -is_rail_crossing:; - rails = GetRailTrackStatus(tile); - - if (rails == TRACK_BIT_HORZ || rails == TRACK_BIT_VERT) return; - - if (rails & TRACK_BIT_3WAY_NE) { -pos_0: - if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(1, 0))) & TRACK_BIT_3WAY_SW) == 0) { - p->ai.cur_dir_a = 0; - p->ai.cur_tile_a = tile; - p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_SE) { -pos_1: - if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(0, 1))) & TRACK_BIT_3WAY_NW) == 0) { - p->ai.cur_dir_a = 1; - p->ai.cur_tile_a = tile; - p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_SW) { -pos_2: - if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(1, 0))) & TRACK_BIT_3WAY_NE) == 0) { - p->ai.cur_dir_a = 2; - p->ai.cur_tile_a = tile; - p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - - if (rails & TRACK_BIT_3WAY_NW) { -pos_3: - if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(0, 1))) & TRACK_BIT_3WAY_SE) == 0) { - p->ai.cur_dir_a = 3; - p->ai.cur_tile_a = tile; - p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; - return; - } - } - } else { - static const byte _depot_bits[] = {0x19,0x16,0x25,0x2A}; - - DiagDirection dir = GetRailDepotDirection(tile); - - if (GetRailTrackStatus(tile + TileOffsByDiagDir(dir)) & _depot_bits[dir]) - return; - - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - } - } else if (IsTileType(tile, MP_STREET)) { - if (!IsTileOwner(tile, _current_player)) return; - - if (IsLevelCrossing(tile)) goto is_rail_crossing; - - if (GetRoadTileType(tile) == ROAD_TILE_DEPOT) { - DiagDirection dir; - TileIndex t; - - // Check if there are any stations around. - t = tile + TileDiffXY(-1, 0); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; - - t = tile + TileDiffXY(1, 0); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; - - t = tile + TileDiffXY(0, -1); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; - - t = tile + TileDiffXY(0, 1); - if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; - - dir = GetRoadDepotDirection(tile); - - DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - DoCommand( - TILE_MASK(tile + TileOffsByDiagDir(dir)), - DiagDirToRoadBits(ReverseDiagDir(dir)), - 0, - DC_EXEC, - CMD_REMOVE_ROAD); - } - } else if (IsTileType(tile, MP_TUNNELBRIDGE)) { - if (!IsTileOwner(tile, _current_player) || - !IsBridge(tile) || - GetBridgeTransportType(tile) != TRANSPORT_RAIL) { - return; - } - - rails = 0; - - switch (GetBridgeRampDirection(tile)) { - default: - case DIAGDIR_NE: goto pos_2; - case DIAGDIR_SE: goto pos_3; - case DIAGDIR_SW: goto pos_0; - case DIAGDIR_NW: goto pos_1; - } - } -} - -static void AiStateRemoveTrack(Player *p) -{ - /* Was 1000 for standard 8x8 maps. */ - int num = MapSizeX() * 4; - - do { - TileIndex tile = ++p->ai.state_counter; - - // Iterated all tiles? - if (tile >= MapSize()) { - p->ai.state = AIS_REMOVE_STATION; - return; - } - - // Remove player stuff in that tile - AiRemovePlayerRailOrRoad(p, tile); - if (p->ai.state != AIS_REMOVE_TRACK) return; - } while (--num); -} - -static void AiStateRemoveSingleRailTile(Player *p) -{ - // Remove until we can't remove more. - if (!AiRemoveTileAndGoForward(p)) p->ai.state = AIS_REMOVE_TRACK; -} - -static AiStateAction * const _ai_actions[] = { - AiCase0, - AiCase1, - AiStateVehLoop, - AiStateCheckReplaceVehicle, - AiStateDoReplaceVehicle, - AiStateWantNewRoute, - - AiStateBuildDefaultRailBlocks, - AiStateBuildRail, - AiStateBuildRailVeh, - AiStateDeleteRailBlocks, - - AiStateBuildDefaultRoadBlocks, - AiStateBuildRoad, - AiStateBuildRoadVehicles, - AiStateDeleteRoadBlocks, - - AiStateAirportStuff, - AiStateBuildDefaultAirportBlocks, - AiStateBuildAircraftVehicles, - - AiStateCheckShipStuff, - AiStateBuildDefaultShipBlocks, - AiStateDoShipStuff, - - AiStateSellVeh, - AiStateRemoveStation, - AiStateRemoveTrack, - - AiStateRemoveSingleRailTile -}; - -extern void ShowBuyCompanyDialog(uint player); - -static void AiHandleTakeover(Player *p) -{ - if (p->bankrupt_timeout != 0) { - p->bankrupt_timeout -= 8; - if (p->bankrupt_timeout > 0) return; - p->bankrupt_timeout = 0; - DeleteWindowById(WC_BUY_COMPANY, _current_player); - if (IsLocalPlayer()) { - AskExitToGameMenu(); - return; - } - if (IsHumanPlayer(_current_player)) return; - } - - if (p->bankrupt_asked == 255) return; - - { - uint asked = p->bankrupt_asked; - Player *pp, *best_pl = NULL; - int32 best_val = -1; - uint old_p; - - // Ask the guy with the highest performance hist. - FOR_ALL_PLAYERS(pp) { - if (pp->is_active && - !(asked&1) && - pp->bankrupt_asked == 0 && - best_val < pp->old_economy[1].performance_history) { - best_val = pp->old_economy[1].performance_history; - best_pl = pp; - } - asked>>=1; - } - - // Asked all players? - if (best_val == -1) { - p->bankrupt_asked = 255; - return; - } - - SETBIT(p->bankrupt_asked, best_pl->index); - - if (best_pl->index == _local_player) { - p->bankrupt_timeout = 4440; - ShowBuyCompanyDialog(_current_player); - return; - } - if (IsHumanPlayer(best_pl->index)) return; - - // Too little money for computer to buy it? - if (best_pl->player_money >> 1 >= p->bankrupt_value) { - // Computer wants to buy it. - old_p = _current_player; - _current_player = p->index; - DoCommand(0, old_p, 0, DC_EXEC, CMD_BUY_COMPANY); - _current_player = old_p; - } - } -} - -static void AiAdjustLoan(const Player* p) -{ - int32 base = AiGetBasePrice(p); - - if (p->player_money > base * 1400) { - // Decrease loan - if (p->current_loan != 0) { - DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); - } - } else if (p->player_money < base * 500) { - // Increase loan - if (p->current_loan < _economy.max_loan && - p->num_valid_stat_ent >= 2 && - -(p->old_economy[0].expenses+p->old_economy[1].expenses) < base * 60) { - DoCommand(0, 0, 0, DC_EXEC, CMD_INCREASE_LOAN); - } - } -} - -static void AiBuildCompanyHQ(Player *p) -{ - TileIndex tile; - - if (p->location_of_house == 0 && - p->last_build_coordinate != 0) { - tile = AdjustTileCoordRandomly(p->last_build_coordinate, 8); - DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); - } -} - - -void AiDoGameLoop(Player *p) -{ - if (p->bankrupt_asked != 0) { - AiHandleTakeover(p); - return; - } - - // Ugly hack to make sure the service interval of the AI is good, not looking - // to the patch-setting - // Also, it takes into account the setting if the service-interval is in days - // or in % - _ai_service_interval = _patches.servint_ispercent?80:180; - - if (IsHumanPlayer(_current_player)) return; - - AiAdjustLoan(p); - AiBuildCompanyHQ(p); - -#if 0 - { - static byte old_state = 99; - static bool hasdots = false; - char *_ai_state_names[]={ - "AiCase0", - "AiCase1", - "AiStateVehLoop", - "AiStateCheckReplaceVehicle", - "AiStateDoReplaceVehicle", - "AiStateWantNewRoute", - "AiStateBuildDefaultRailBlocks", - "AiStateBuildRail", - "AiStateBuildRailVeh", - "AiStateDeleteRailBlocks", - "AiStateBuildDefaultRoadBlocks", - "AiStateBuildRoad", - "AiStateBuildRoadVehicles", - "AiStateDeleteRoadBlocks", - "AiStateAirportStuff", - "AiStateBuildDefaultAirportBlocks", - "AiStateBuildAircraftVehicles", - "AiStateCheckShipStuff", - "AiStateBuildDefaultShipBlocks", - "AiStateDoShipStuff", - "AiStateSellVeh", - "AiStateRemoveStation", - "AiStateRemoveTrack", - "AiStateRemoveSingleRailTile" - }; - - if (p->ai.state != old_state) { - if (hasdots) - printf("\n"); - hasdots=false; - printf("AiState: %s\n", _ai_state_names[old_state=p->ai.state]); - } else { - printf("."); - hasdots=true; - } - } -#endif - - _ai_actions[p->ai.state](p); -} diff --git a/src/ai/default/default.cpp b/src/ai/default/default.cpp new file mode 100644 --- /dev/null +++ b/src/ai/default/default.cpp @@ -0,0 +1,3934 @@ +/* $Id$ */ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../aircraft.h" +#include "../../bridge_map.h" +#include "../../functions.h" +#include "../../map.h" +#include "../../rail_map.h" +#include "../../road_map.h" +#include "../../roadveh.h" +#include "../../station_map.h" +#include "../../tile.h" +#include "../../player.h" +#include "../../tunnel_map.h" +#include "../../vehicle.h" +#include "../../engine.h" +#include "../../command.h" +#include "../../town.h" +#include "../../industry.h" +#include "../../station.h" +#include "../../pathfind.h" +#include "../../economy.h" +#include "../../airport.h" +#include "../../depot.h" +#include "../../variables.h" +#include "../../bridge.h" +#include "../../date.h" +#include "default.h" + +// remove some day perhaps? +static uint _ai_service_interval; + +typedef void AiStateAction(Player *p); + +enum { + AIS_0 = 0, + AIS_1 = 1, + AIS_VEH_LOOP = 2, + AIS_VEH_CHECK_REPLACE_VEHICLE = 3, + AIS_VEH_DO_REPLACE_VEHICLE = 4, + AIS_WANT_NEW_ROUTE = 5, + AIS_BUILD_DEFAULT_RAIL_BLOCKS = 6, + AIS_BUILD_RAIL = 7, + AIS_BUILD_RAIL_VEH = 8, + AIS_DELETE_RAIL_BLOCKS = 9, + AIS_BUILD_DEFAULT_ROAD_BLOCKS = 10, + AIS_BUILD_ROAD = 11, + AIS_BUILD_ROAD_VEHICLES = 12, + AIS_DELETE_ROAD_BLOCKS = 13, + AIS_AIRPORT_STUFF = 14, + AIS_BUILD_DEFAULT_AIRPORT_BLOCKS = 15, + AIS_BUILD_AIRCRAFT_VEHICLES = 16, + AIS_CHECK_SHIP_STUFF = 17, + AIS_BUILD_DEFAULT_SHIP_BLOCKS = 18, + AIS_DO_SHIP_STUFF = 19, + AIS_SELL_VEHICLE = 20, + AIS_REMOVE_STATION = 21, + AIS_REMOVE_TRACK = 22, + AIS_REMOVE_SINGLE_RAIL_TILE = 23 +}; + + +#include "../../table/ai_rail.h" + +static byte GetRailTrackStatus(TileIndex tile) +{ + uint32 r = GetTileTrackStatus(tile, TRANSPORT_RAIL); + return (byte) (r | r >> 8); +} + + +static void AiCase0(Player *p) +{ + p->ai.state = AIS_REMOVE_TRACK; + p->ai.state_counter = 0; +} + +static void AiCase1(Player *p) +{ + p->ai.cur_veh = NULL; + p->ai.state = AIS_VEH_LOOP; +} + +static void AiStateVehLoop(Player *p) +{ + Vehicle *v; + uint index; + + index = (p->ai.cur_veh == NULL) ? 0 : p->ai.cur_veh->index + 1; + + FOR_ALL_VEHICLES_FROM(v, index) { + if (v->owner != _current_player) continue; + + if ((v->type == VEH_Train && v->subtype == 0) || + v->type == VEH_Road || + (v->type == VEH_Aircraft && v->subtype <= 2) || + v->type == VEH_Ship) { + /* replace engine? */ + if (v->type == VEH_Train && v->engine_type < 3 && + (_price.build_railvehicle >> 3) < p->player_money) { + p->ai.state = AIS_VEH_CHECK_REPLACE_VEHICLE; + p->ai.cur_veh = v; + return; + } + + /* not profitable? */ + if (v->age >= 730 && + v->profit_last_year < _price.station_value * 5 && + v->profit_this_year < _price.station_value * 5) { + p->ai.state_counter = 0; + p->ai.state = AIS_SELL_VEHICLE; + p->ai.cur_veh = v; + return; + } + + /* not reliable? */ + if (v->age >= v->max_age || ( + v->age != 0 && + GetEngine(v->engine_type)->reliability < 35389 + )) { + p->ai.state = AIS_VEH_CHECK_REPLACE_VEHICLE; + p->ai.cur_veh = v; + return; + } + } + } + + p->ai.state = AIS_WANT_NEW_ROUTE; + p->ai.state_counter = 0; +} + +static EngineID AiChooseTrainToBuild(RailType railtype, int32 money, byte flag, TileIndex tile) +{ + EngineID best_veh_index = INVALID_ENGINE; + byte best_veh_score = 0; + int32 ret; + EngineID i; + + for (i = 0; i < NUM_TRAIN_ENGINES; i++) { + const RailVehicleInfo *rvi = RailVehInfo(i); + const Engine* e = GetEngine(i); + + if (!IsCompatibleRail(e->railtype, railtype) || + rvi->flags & RVI_WAGON || + (rvi->flags & RVI_MULTIHEAD && flag & 1) || + !HASBIT(e->player_avail, _current_player) || + e->reliability < 0x8A3D) { + continue; + } + + ret = DoCommand(tile, i, 0, 0, CMD_BUILD_RAIL_VEHICLE); + if (!CmdFailed(ret) && ret <= money && rvi->ai_rank >= best_veh_score) { + best_veh_score = rvi->ai_rank; + best_veh_index = i; + } + } + + return best_veh_index; +} + +static EngineID AiChooseRoadVehToBuild(CargoID cargo, int32 money, TileIndex tile) +{ + EngineID best_veh_index = INVALID_ENGINE; + int32 best_veh_rating = 0; + EngineID i = ROAD_ENGINES_INDEX; + EngineID end = i + NUM_ROAD_ENGINES; + + for (; i != end; i++) { + const RoadVehicleInfo *rvi = RoadVehInfo(i); + const Engine* e = GetEngine(i); + int32 rating; + int32 ret; + + if (!HASBIT(e->player_avail, _current_player) || e->reliability < 0x8A3D) { + continue; + } + + /* Skip vehicles which can't take our cargo type */ + if (rvi->cargo_type != cargo && !CanRefitTo(i, cargo)) continue; + + /* Rate and compare the engine by speed & capacity */ + rating = rvi->max_speed * rvi->capacity; + if (rating <= best_veh_rating) continue; + + ret = DoCommand(tile, i, 0, 0, CMD_BUILD_ROAD_VEH); + if (CmdFailed(ret)) continue; + + /* Add the cost of refitting */ + if (rvi->cargo_type != cargo) ret += GetRefitCost(i); + if (ret > money) continue; + + best_veh_rating = rating; + best_veh_index = i; + } + + return best_veh_index; +} + +static EngineID AiChooseAircraftToBuild(int32 money, byte flag) +{ + EngineID best_veh_index = INVALID_ENGINE; + int32 best_veh_cost = 0; + EngineID i; + + for (i = AIRCRAFT_ENGINES_INDEX; i != AIRCRAFT_ENGINES_INDEX + NUM_AIRCRAFT_ENGINES; i++) { + const Engine* e = GetEngine(i); + int32 ret; + + if (!HASBIT(e->player_avail, _current_player) || e->reliability < 0x8A3D) { + continue; + } + + if ((AircraftVehInfo(i)->subtype & AIR_CTOL) != flag) continue; + + ret = DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_AIRCRAFT); + if (!CmdFailed(ret) && ret <= money && ret >= best_veh_cost) { + best_veh_cost = ret; + best_veh_index = i; + } + } + + return best_veh_index; +} + +static int32 AiGetBasePrice(const Player* p) +{ + int32 base = _price.station_value; + + // adjust base price when more expensive vehicles are available + switch (p->ai.railtype_to_use) { + default: NOT_REACHED(); + case RAILTYPE_RAIL: break; + case RAILTYPE_ELECTRIC: break; + case RAILTYPE_MONO: base = (base * 3) >> 1; break; + case RAILTYPE_MAGLEV: base *= 2; break; + } + + return base; +} + +#if 0 +static EngineID AiChooseShipToBuild(byte cargo, int32 money) +{ + // XXX: not done + return INVALID_ENGINE; +} +#endif + +static EngineID AiChooseRoadVehToReplaceWith(const Player* p, const Vehicle* v) +{ + int32 avail_money = p->player_money + v->value; + return AiChooseRoadVehToBuild(v->cargo_type, avail_money, v->tile); +} + +static EngineID AiChooseAircraftToReplaceWith(const Player* p, const Vehicle* v) +{ + int32 avail_money = p->player_money + v->value; + return AiChooseAircraftToBuild( + avail_money, AircraftVehInfo(v->engine_type)->subtype & AIR_CTOL + ); +} + +static EngineID AiChooseTrainToReplaceWith(const Player* p, const Vehicle* v) +{ + int32 avail_money = p->player_money + v->value; + const Vehicle* u = v; + int num = 0; + + while (++num, u->next != NULL) { + u = u->next; + } + + // XXX: check if a wagon + return AiChooseTrainToBuild(v->u.rail.railtype, avail_money, 0, v->tile); +} + +static EngineID AiChooseShipToReplaceWith(const Player* p, const Vehicle* v) +{ + error("!AiChooseShipToReplaceWith"); + + /* maybe useless, but avoids compiler warning this way */ + return INVALID_ENGINE; +} + +static void AiHandleGotoDepot(Player *p, int cmd) +{ + if (p->ai.cur_veh->current_order.type != OT_GOTO_DEPOT) + DoCommand(0, p->ai.cur_veh->index, 0, DC_EXEC, cmd); + + if (++p->ai.state_counter <= 1387) { + p->ai.state = AIS_VEH_DO_REPLACE_VEHICLE; + return; + } + + if (p->ai.cur_veh->current_order.type == OT_GOTO_DEPOT) { + p->ai.cur_veh->current_order.type = OT_DUMMY; + p->ai.cur_veh->current_order.flags = 0; + InvalidateWindow(WC_VEHICLE_VIEW, p->ai.cur_veh->index); + } +} + +static void AiRestoreVehicleOrders(Vehicle *v, BackuppedOrders *bak) +{ + uint i; + + for (i = 0; bak->order[i].type != OT_NOTHING; i++) { + if (!DoCommandP(0, v->index + (i << 16), PackOrder(&bak->order[i]), NULL, CMD_INSERT_ORDER | CMD_NO_TEST_IF_IN_NETWORK)) + break; + } +} + +static void AiHandleReplaceTrain(Player *p) +{ + const Vehicle* v = p->ai.cur_veh; + BackuppedOrders orderbak[1]; + EngineID veh; + + // wait until the vehicle reaches the depot. + if (!IsTileDepotType(v->tile, TRANSPORT_RAIL) || v->u.rail.track != 0x80 || !(v->vehstatus&VS_STOPPED)) { + AiHandleGotoDepot(p, CMD_SEND_TRAIN_TO_DEPOT); + return; + } + + veh = AiChooseTrainToReplaceWith(p, v); + if (veh != INVALID_ENGINE) { + TileIndex tile; + + BackupVehicleOrders(v, orderbak); + tile = v->tile; + + if (!CmdFailed(DoCommand(0, v->index, 2, DC_EXEC, CMD_SELL_RAIL_WAGON)) && + !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE))) { + VehicleID veh = _new_vehicle_id; + AiRestoreVehicleOrders(GetVehicle(veh), orderbak); + DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_TRAIN); + + DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + } + } +} + +static void AiHandleReplaceRoadVeh(Player *p) +{ + const Vehicle* v = p->ai.cur_veh; + BackuppedOrders orderbak[1]; + EngineID veh; + + if (!IsRoadVehInDepotStopped(v)) { + AiHandleGotoDepot(p, CMD_SEND_ROADVEH_TO_DEPOT); + return; + } + + veh = AiChooseRoadVehToReplaceWith(p, v); + if (veh != INVALID_ENGINE) { + TileIndex tile; + + BackupVehicleOrders(v, orderbak); + tile = v->tile; + + if (!CmdFailed(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH)) && + !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) { + VehicleID veh = _new_vehicle_id; + + AiRestoreVehicleOrders(GetVehicle(veh), orderbak); + DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_ROADVEH); + DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + } + } +} + +static void AiHandleReplaceAircraft(Player *p) +{ + const Vehicle* v = p->ai.cur_veh; + BackuppedOrders orderbak[1]; + EngineID veh; + + if (!IsAircraftInHangarStopped(v)) { + AiHandleGotoDepot(p, CMD_SEND_AIRCRAFT_TO_HANGAR); + return; + } + + veh = AiChooseAircraftToReplaceWith(p, v); + if (veh != INVALID_ENGINE) { + TileIndex tile; + + BackupVehicleOrders(v, orderbak); + tile = v->tile; + + if (!CmdFailed(DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT)) && + !CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) { + VehicleID veh = _new_vehicle_id; + AiRestoreVehicleOrders(GetVehicle(veh), orderbak); + DoCommand(0, veh, 0, DC_EXEC, CMD_START_STOP_AIRCRAFT); + + DoCommand(0, veh, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + } + } +} + +static void AiHandleReplaceShip(Player *p) +{ + error("!AiHandleReplaceShip"); +} + +typedef EngineID CheckReplaceProc(const Player* p, const Vehicle* v); + +static CheckReplaceProc* const _veh_check_replace_proc[] = { + AiChooseTrainToReplaceWith, + AiChooseRoadVehToReplaceWith, + AiChooseShipToReplaceWith, + AiChooseAircraftToReplaceWith, +}; + +typedef void DoReplaceProc(Player *p); +static DoReplaceProc* const _veh_do_replace_proc[] = { + AiHandleReplaceTrain, + AiHandleReplaceRoadVeh, + AiHandleReplaceShip, + AiHandleReplaceAircraft +}; + +static void AiStateCheckReplaceVehicle(Player *p) +{ + const Vehicle* v = p->ai.cur_veh; + + if (!IsValidVehicle(v) || + v->owner != _current_player || + v->type > VEH_Ship || + _veh_check_replace_proc[v->type - VEH_Train](p, v) == INVALID_ENGINE) { + p->ai.state = AIS_VEH_LOOP; + } else { + p->ai.state_counter = 0; + p->ai.state = AIS_VEH_DO_REPLACE_VEHICLE; + } +} + +static void AiStateDoReplaceVehicle(Player *p) +{ + const Vehicle* v = p->ai.cur_veh; + + p->ai.state = AIS_VEH_LOOP; + // vehicle is not owned by the player anymore, something went very wrong. + if (!IsValidVehicle(v) || v->owner != _current_player) return; + _veh_do_replace_proc[v->type - VEH_Train](p); +} + +typedef struct FoundRoute { + int distance; + CargoID cargo; + void *from; + void *to; +} FoundRoute; + +static Town *AiFindRandomTown(void) +{ + return GetRandomTown(); +} + +static Industry *AiFindRandomIndustry(void) +{ + return GetRandomIndustry(); +} + +static void AiFindSubsidyIndustryRoute(FoundRoute *fr) +{ + uint i; + CargoID cargo; + const Subsidy* s; + Industry* from; + TileIndex to_xy; + + // initially error + fr->distance = -1; + + // Randomize subsidy index.. + i = RandomRange(lengthof(_subsidies) * 3); + if (i >= lengthof(_subsidies)) return; + + s = &_subsidies[i]; + + // Don't want passengers or mail + cargo = s->cargo_type; + if (cargo == CT_INVALID || + cargo == CT_PASSENGERS || + cargo == CT_MAIL || + s->age > 7) { + return; + } + fr->cargo = cargo; + + fr->from = from = GetIndustry(s->from); + + if (cargo == CT_GOODS || cargo == CT_FOOD) { + Town* to_tow = GetTown(s->to); + + if (to_tow->population < (cargo == CT_FOOD ? 200U : 900U)) return; // error + fr->to = to_tow; + to_xy = to_tow->xy; + } else { + Industry* to_ind = GetIndustry(s->to); + + fr->to = to_ind; + to_xy = to_ind->xy; + } + + fr->distance = DistanceManhattan(from->xy, to_xy); +} + +static void AiFindSubsidyPassengerRoute(FoundRoute *fr) +{ + uint i; + const Subsidy* s; + Town *from,*to; + + // initially error + fr->distance = -1; + + // Randomize subsidy index.. + i = RandomRange(lengthof(_subsidies) * 3); + if (i >= lengthof(_subsidies)) return; + + s = &_subsidies[i]; + + // Only want passengers + if (s->cargo_type != CT_PASSENGERS || s->age > 7) return; + fr->cargo = s->cargo_type; + + fr->from = from = GetTown(s->from); + fr->to = to = GetTown(s->to); + + // They must be big enough + if (from->population < 400 || to->population < 400) return; + + fr->distance = DistanceManhattan(from->xy, to->xy); +} + +static void AiFindRandomIndustryRoute(FoundRoute *fr) +{ + Industry* i; + uint32 r; + CargoID cargo; + + // initially error + fr->distance = -1; + + r = Random(); + + // pick a source + fr->from = i = AiFindRandomIndustry(); + if (i == NULL) return; + + // pick a random produced cargo + cargo = i->produced_cargo[0]; + if (r & 1 && i->produced_cargo[1] != CT_INVALID) cargo = i->produced_cargo[1]; + + fr->cargo = cargo; + + // don't allow passengers + if (cargo == CT_INVALID || cargo == CT_PASSENGERS) return; + + if (cargo != CT_GOODS && cargo != CT_FOOD) { + // pick a dest, and see if it can receive + Industry* i2 = AiFindRandomIndustry(); + + if (i2 == NULL || i == i2 || ( + i2->accepts_cargo[0] != cargo && + i2->accepts_cargo[1] != cargo && + i2->accepts_cargo[2] != cargo) + ) { + return; + } + + fr->to = i2; + fr->distance = DistanceManhattan(i->xy, i2->xy); + } else { + // pick a dest town, and see if it's big enough + Town* t = AiFindRandomTown(); + + if (t == NULL || t->population < (cargo == CT_FOOD ? 200U : 900U)) return; + + fr->to = t; + fr->distance = DistanceManhattan(i->xy, t->xy); + } +} + +static void AiFindRandomPassengerRoute(FoundRoute *fr) +{ + Town* source; + Town* dest; + + // initially error + fr->distance = -1; + + fr->from = source = AiFindRandomTown(); + if (source == NULL || source->population < 400) return; + + fr->to = dest = AiFindRandomTown(); + if (dest == NULL || source == dest || dest->population < 400) return; + + fr->distance = DistanceManhattan(source->xy, dest->xy); +} + +// Warn: depends on 'xy' being the first element in both Town and Industry +#define GET_TOWN_OR_INDUSTRY_TILE(p) (((Town*)(p))->xy) + +static bool AiCheckIfRouteIsGood(Player *p, FoundRoute *fr, byte bitmask) +{ + TileIndex from_tile, to_tile; + Station *st; + int dist; + uint same_station = 0; + + // Make sure distance to closest station is < 37 pixels. + from_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->from); + to_tile = GET_TOWN_OR_INDUSTRY_TILE(fr->to); + + dist = 0xFFFF; + FOR_ALL_STATIONS(st) { + int cur; + + if (st->owner != _current_player) continue; + cur = DistanceMax(from_tile, st->xy); + if (cur < dist) dist = cur; + cur = DistanceMax(to_tile, st->xy); + if (cur < dist) dist = cur; + if (to_tile == from_tile && st->xy == to_tile) same_station++; + } + + // To prevent the AI from building ten busstations in the same town, do some calculations + // For each road or airport station, we want 350 of population! + if ((bitmask == 2 || bitmask == 4) && + same_station > 2 && + ((Town*)fr->from)->population < same_station * 350) { + return false; + } + + if (dist != 0xFFFF && dist > 37) return false; + + if (p->ai.route_type_mask != 0 && + !(p->ai.route_type_mask & bitmask) && + !CHANCE16(1, 5)) { + return false; + } + + if (fr->cargo == CT_PASSENGERS || fr->cargo == CT_MAIL) { + const Town* from = fr->from; + const Town* to = fr->to; + + if (from->pct_pass_transported > 0x99 || + to->pct_pass_transported > 0x99) { + return false; + } + + // Make sure it has a reasonably good rating + if (from->ratings[_current_player] < -100 || + to->ratings[_current_player] < -100) { + return false; + } + } else { + const Industry* i = (const Industry*)fr->from; + + if (i->pct_transported[fr->cargo != i->produced_cargo[0]] > 0x99 || + i->total_production[fr->cargo != i->produced_cargo[0]] == 0) { + return false; + } + } + + p->ai.route_type_mask |= bitmask; + return true; +} + +static byte AiGetDirectionBetweenTiles(TileIndex a, TileIndex b) +{ + byte i = (TileX(a) < TileX(b)) ? 1 : 0; + if (TileY(a) >= TileY(b)) i ^= 3; + return i; +} + +static TileIndex AiGetPctTileBetween(TileIndex a, TileIndex b, byte pct) +{ + return TileXY( + TileX(a) + ((TileX(b) - TileX(a)) * pct >> 8), + TileY(a) + ((TileY(b) - TileY(a)) * pct >> 8) + ); +} + +static void AiWantLongIndustryRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 60, 90 + 1)) break; + + // try a random one + AiFindRandomIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 60, 90 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; + + // Fill the source field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 9; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.unk6 = 1; + p->ai.src.unk7 = 0; + p->ai.src.buildcmd_a = 0x24; + p->ai.src.buildcmd_b = 0xFF; + p->ai.src.direction = AiGetDirectionBetweenTiles( + p->ai.src.spec_tile, + p->ai.dst.spec_tile + ); + p->ai.src.cargo = fr.cargo | 0x80; + + // Fill the dest field + + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 9; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.unk6 = 1; + p->ai.dst.unk7 = 0; + p->ai.dst.buildcmd_a = 0x34; + p->ai.dst.buildcmd_b = 0xFF; + p->ai.dst.direction = AiGetDirectionBetweenTiles( + p->ai.dst.spec_tile, + p->ai.src.spec_tile + ); + p->ai.dst.cargo = fr.cargo; + + // Fill middle field 1 + p->ai.mid1.spec_tile = AiGetPctTileBetween( + p->ai.src.spec_tile, + p->ai.dst.spec_tile, + 0x55 + ); + p->ai.mid1.use_tile = 0; + p->ai.mid1.rand_rng = 6; + p->ai.mid1.cur_building_rule = 0xFF; + p->ai.mid1.unk6 = 2; + p->ai.mid1.unk7 = 1; + p->ai.mid1.buildcmd_a = 0x30; + p->ai.mid1.buildcmd_b = 0xFF; + p->ai.mid1.direction = p->ai.src.direction; + p->ai.mid1.cargo = fr.cargo; + + // Fill middle field 2 + p->ai.mid2.spec_tile = AiGetPctTileBetween( + p->ai.src.spec_tile, + p->ai.dst.spec_tile, + 0xAA + ); + p->ai.mid2.use_tile = 0; + p->ai.mid2.rand_rng = 6; + p->ai.mid2.cur_building_rule = 0xFF; + p->ai.mid2.unk6 = 2; + p->ai.mid2.unk7 = 1; + p->ai.mid2.buildcmd_a = 0xFF; + p->ai.mid2.buildcmd_b = 0xFF; + p->ai.mid2.direction = p->ai.dst.direction; + p->ai.mid2.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_wagons = 3; + p->ai.build_kind = 2; + p->ai.num_build_rec = 4; + p->ai.num_loco_to_build = 2; + p->ai.num_want_fullload = 2; + p->ai.wagon_list[0] = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantMediumIndustryRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 40, 60 + 1)) break; + + // try a random one + AiFindRandomIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 40, 60 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 9; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.unk6 = 1; + p->ai.src.unk7 = 0; + p->ai.src.buildcmd_a = 0x10; + p->ai.src.buildcmd_b = 0xFF; + p->ai.src.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to) + ); + p->ai.src.cargo = fr.cargo | 0x80; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 9; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.unk6 = 1; + p->ai.dst.unk7 = 0; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.buildcmd_b = 0xFF; + p->ai.dst.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + GET_TOWN_OR_INDUSTRY_TILE(fr.from) + ); + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_wagons = 3; + p->ai.build_kind = 1; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 1; + p->ai.num_want_fullload = 1; + p->ai.wagon_list[0] = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantShortIndustryRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; + + // try a random one + AiFindRandomIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 9; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.unk6 = 1; + p->ai.src.unk7 = 0; + p->ai.src.buildcmd_a = 0x10; + p->ai.src.buildcmd_b = 0xFF; + p->ai.src.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to) + ); + p->ai.src.cargo = fr.cargo | 0x80; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 9; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.unk6 = 1; + p->ai.dst.unk7 = 0; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.buildcmd_b = 0xFF; + p->ai.dst.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + GET_TOWN_OR_INDUSTRY_TILE(fr.from) + ); + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_wagons = 2; + p->ai.build_kind = 1; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 1; + p->ai.num_want_fullload = 1; + p->ai.wagon_list[0] = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantMailRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 60, 110 + 1)) break; + + // try a random one + AiFindRandomPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 60, 110 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_MAIL; + if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 7; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.unk6 = 1; + p->ai.src.unk7 = 0; + p->ai.src.buildcmd_a = 0x24; + p->ai.src.buildcmd_b = 0xFF; + p->ai.src.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to) + ); + p->ai.src.cargo = fr.cargo; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 7; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.unk6 = 1; + p->ai.dst.unk7 = 0; + p->ai.dst.buildcmd_a = 0x34; + p->ai.dst.buildcmd_b = 0xFF; + p->ai.dst.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + GET_TOWN_OR_INDUSTRY_TILE(fr.from) + ); + p->ai.dst.cargo = fr.cargo; + + // Fill middle field 1 + p->ai.mid1.spec_tile = AiGetPctTileBetween( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + 0x55 + ); + p->ai.mid1.use_tile = 0; + p->ai.mid1.rand_rng = 6; + p->ai.mid1.cur_building_rule = 0xFF; + p->ai.mid1.unk6 = 2; + p->ai.mid1.unk7 = 1; + p->ai.mid1.buildcmd_a = 0x30; + p->ai.mid1.buildcmd_b = 0xFF; + p->ai.mid1.direction = p->ai.src.direction; + p->ai.mid1.cargo = fr.cargo; + + // Fill middle field 2 + p->ai.mid2.spec_tile = AiGetPctTileBetween( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + 0xAA + ); + p->ai.mid2.use_tile = 0; + p->ai.mid2.rand_rng = 6; + p->ai.mid2.cur_building_rule = 0xFF; + p->ai.mid2.unk6 = 2; + p->ai.mid2.unk7 = 1; + p->ai.mid2.buildcmd_a = 0xFF; + p->ai.mid2.buildcmd_b = 0xFF; + p->ai.mid2.direction = p->ai.dst.direction; + p->ai.mid2.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_wagons = 3; + p->ai.build_kind = 2; + p->ai.num_build_rec = 4; + p->ai.num_loco_to_build = 2; + p->ai.num_want_fullload = 0; + p->ai.wagon_list[0] = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantPassengerRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 0, 55 + 1)) break; + + // try a random one + AiFindRandomPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 0, 55 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_PASSENGERS; + if (!AiCheckIfRouteIsGood(p, &fr, 1)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 7; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.unk6 = 1; + p->ai.src.unk7 = 0; + p->ai.src.buildcmd_a = 0x10; + p->ai.src.buildcmd_b = 0xFF; + p->ai.src.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.from), + GET_TOWN_OR_INDUSTRY_TILE(fr.to) + ); + p->ai.src.cargo = fr.cargo; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 7; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.unk6 = 1; + p->ai.dst.unk7 = 0; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.buildcmd_b = 0xFF; + p->ai.dst.direction = AiGetDirectionBetweenTiles( + GET_TOWN_OR_INDUSTRY_TILE(fr.to), + GET_TOWN_OR_INDUSTRY_TILE(fr.from) + ); + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_wagons = 2; + p->ai.build_kind = 1; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 1; + p->ai.num_want_fullload = 0; + p->ai.wagon_list[0] = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + p->ai.state = AIS_BUILD_DEFAULT_RAIL_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantTrainRoute(Player *p) +{ + uint16 r = GB(Random(), 0, 16); + + p->ai.railtype_to_use = GetBestRailtype(p); + + if (r > 0xD000) { + AiWantLongIndustryRoute(p); + } else if (r > 0x6000) { + AiWantMediumIndustryRoute(p); + } else if (r > 0x1000) { + AiWantShortIndustryRoute(p); + } else if (r > 0x800) { + AiWantPassengerRoute(p); + } else { + AiWantMailRoute(p); + } +} + +static void AiWantLongRoadIndustryRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 35, 55 + 1)) break; + + // try a random one + AiFindRandomIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 35, 55 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 9; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.buildcmd_a = 1; + p->ai.src.direction = 0; + p->ai.src.cargo = fr.cargo | 0x80; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 9; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.direction = 0; + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 5; + p->ai.num_want_fullload = 5; + +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantMediumRoadIndustryRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; + + // try a random one + AiFindRandomIndustryRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 15, 40 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 9; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.buildcmd_a = 1; + p->ai.src.direction = 0; + p->ai.src.cargo = fr.cargo | 0x80; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 9; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.direction = 0; + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 3; + p->ai.num_want_fullload = 3; + +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantLongRoadPassengerRoute(Player *p) +{ + int i; + FoundRoute fr; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 55, 180 + 1)) break; + + // try a random one + AiFindRandomPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 55, 180 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_PASSENGERS; + + if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 10; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.buildcmd_a = 1; + p->ai.src.direction = 0; + p->ai.src.cargo = CT_PASSENGERS; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 10; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.direction = 0; + p->ai.dst.cargo = CT_PASSENGERS; + + // Fill common fields + p->ai.cargo_type = CT_PASSENGERS; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 4; + p->ai.num_want_fullload = 0; + +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantPassengerRouteInsideTown(Player *p) +{ + int i; + FoundRoute fr; + Town *t; + + i = 60; + for (;;) { + // Find a town big enough + t = AiFindRandomTown(); + if (t != NULL && t->population >= 700) break; + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_PASSENGERS; + fr.from = fr.to = t; + + if (!AiCheckIfRouteIsGood(p, &fr, 2)) return; + + // Fill the source field + p->ai.src.spec_tile = t->xy; + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 10; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.buildcmd_a = 1; + p->ai.src.direction = 0; + p->ai.src.cargo = CT_PASSENGERS; + + // Fill the dest field + p->ai.dst.spec_tile = t->xy; + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 10; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.buildcmd_a = 0xFF; + p->ai.dst.direction = 0; + p->ai.dst.cargo = CT_PASSENGERS; + + // Fill common fields + p->ai.cargo_type = CT_PASSENGERS; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 2; + p->ai.num_want_fullload = 0; + +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_BUILD_DEFAULT_ROAD_BLOCKS; + p->ai.state_mode = -1; + p->ai.state_counter = 0; + p->ai.timeout_counter = 0; +} + +static void AiWantRoadRoute(Player *p) +{ + uint16 r = GB(Random(), 0, 16); + + if (r > 0x4000) { + AiWantLongRoadIndustryRoute(p); + } else if (r > 0x2000) { + AiWantMediumRoadIndustryRoute(p); + } else if (r > 0x1000) { + AiWantLongRoadPassengerRoute(p); + } else { + AiWantPassengerRouteInsideTown(p); + } +} + +static void AiWantPassengerAircraftRoute(Player *p) +{ + FoundRoute fr; + int i; + + i = 60; + for (;;) { + // look for one from the subsidy list + AiFindSubsidyPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 0, 95 + 1)) break; + + // try a random one + AiFindRandomPassengerRoute(&fr); + if (IS_INT_INSIDE(fr.distance, 0, 95 + 1)) break; + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_PASSENGERS; + if (!AiCheckIfRouteIsGood(p, &fr, 4)) return; + + + // Fill the source field + p->ai.src.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.to); + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 12; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.cargo = fr.cargo; + + // Fill the dest field + p->ai.dst.spec_tile = GET_TOWN_OR_INDUSTRY_TILE(fr.from); + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 12; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.cargo = fr.cargo; + + // Fill common fields + p->ai.cargo_type = fr.cargo; + p->ai.build_kind = 0; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 1; + p->ai.num_want_fullload = 1; +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_AIRPORT_STUFF; + p->ai.timeout_counter = 0; +} + +static void AiWantOilRigAircraftRoute(Player *p) +{ + int i; + FoundRoute fr; + Town *t; + Industry *in; + + i = 60; + for (;;) { + // Find a town + t = AiFindRandomTown(); + if (t != NULL) { + // Find a random oil rig industry + in = AiFindRandomIndustry(); + if (in != NULL && in->type == IT_OIL_RIG) { + if (DistanceManhattan(t->xy, in->xy) < 60) + break; + } + } + + // only test 60 times + if (--i == 0) return; + } + + fr.cargo = CT_PASSENGERS; + fr.from = fr.to = t; + + if (!AiCheckIfRouteIsGood(p, &fr, 4)) return; + + // Fill the source field + p->ai.src.spec_tile = t->xy; + p->ai.src.use_tile = 0; + p->ai.src.rand_rng = 12; + p->ai.src.cur_building_rule = 0xFF; + p->ai.src.cargo = CT_PASSENGERS; + + // Fill the dest field + p->ai.dst.spec_tile = in->xy; + p->ai.dst.use_tile = 0; + p->ai.dst.rand_rng = 5; + p->ai.dst.cur_building_rule = 0xFF; + p->ai.dst.cargo = CT_PASSENGERS; + + // Fill common fields + p->ai.cargo_type = CT_PASSENGERS; + p->ai.build_kind = 1; + p->ai.num_build_rec = 2; + p->ai.num_loco_to_build = 1; + p->ai.num_want_fullload = 0; +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.order_list_blocks[0] = 0; + p->ai.order_list_blocks[1] = 1; + p->ai.order_list_blocks[2] = 255; + + p->ai.state = AIS_AIRPORT_STUFF; + p->ai.timeout_counter = 0; +} + +static void AiWantAircraftRoute(Player *p) +{ + uint16 r = (uint16)Random(); + + if (r >= 0x2AAA || _date < 0x3912 + DAYS_TILL_ORIGINAL_BASE_YEAR) { + AiWantPassengerAircraftRoute(p); + } else { + AiWantOilRigAircraftRoute(p); + } +} + +static void AiWantShipRoute(Player *p) +{ + // XXX +// error("AiWaitShipRoute"); +} + + + +static void AiStateWantNewRoute(Player *p) +{ + uint16 r; + int i; + + if (p->player_money < AiGetBasePrice(p) * 500) { + p->ai.state = AIS_0; + return; + } + + i = 200; + for (;;) { + r = (uint16)Random(); + + if (_patches.ai_disable_veh_train && + _patches.ai_disable_veh_roadveh && + _patches.ai_disable_veh_aircraft && + _patches.ai_disable_veh_ship) { + return; + } + + if (r < 0x7626) { + if (_patches.ai_disable_veh_train) continue; + AiWantTrainRoute(p); + } else if (r < 0xC4EA) { + if (_patches.ai_disable_veh_roadveh) continue; + AiWantRoadRoute(p); + } else if (r < 0xD89B) { + if (_patches.ai_disable_veh_aircraft) continue; + AiWantAircraftRoute(p); + } else { + if (_patches.ai_disable_veh_ship) continue; + AiWantShipRoute(p); + } + + // got a route? + if (p->ai.state != AIS_WANT_NEW_ROUTE) break; + + // time out? + if (--i == 0) { + if (++p->ai.state_counter == 556) p->ai.state = AIS_0; + break; + } + } +} + +static bool AiCheckTrackResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) +{ + uint rad = (_patches.modified_catchment) ? CA_TRAIN : 4; + + for (; p->mode != 4; p++) { + AcceptedCargo values; + TileIndex tile2; + uint w; + uint h; + + if (p->mode != 1) continue; + + tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); + w = GB(p->attr, 1, 3); + h = GB(p->attr, 4, 3); + + if (p->attr & 1) uintswap(w, h); + + if (cargo & 0x80) { + GetProductionAroundTiles(values, tile2, w, h, rad); + return values[cargo & 0x7F] != 0; + } else { + GetAcceptanceAroundTiles(values, tile2, w, h, rad); + if (!(values[cargo] & ~7)) + return false; + if (cargo != CT_MAIL) + return true; + return !!((values[cargo]>>1) & ~7); + } + } + + return true; +} + +static int32 AiDoBuildDefaultRailTrack(TileIndex tile, const AiDefaultBlockData* p, RailType railtype, byte flag) +{ + int32 ret; + int32 total_cost = 0; + Town *t = NULL; + int rating = 0; + int i,j,k; + + for (;;) { + // This will seldomly overflow for valid reasons. Mask it to be on the safe side. + uint c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); + + _cleared_town = NULL; + + if (p->mode < 2) { + if (p->mode == 0) { + // Depot + ret = DoCommand(c, railtype, p->attr, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_TRAIN_DEPOT); + } else { + // Station + ret = DoCommand(c, (p->attr&1) | (p->attr>>4)<<8 | (p->attr>>1&7)<<16, railtype, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_RAILROAD_STATION); + } + + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + +clear_town_stuff:; + if (_cleared_town != NULL) { + if (t != NULL && t != _cleared_town) + return CMD_ERROR; + t = _cleared_town; + rating += _cleared_town_rating; + } + } else if (p->mode == 2) { + // Rail + if (IsTileType(c, MP_RAILWAY)) return CMD_ERROR; + + j = p->attr; + k = 0; + + // Build the rail + for (i = 0; i != 6; i++, j >>= 1) { + if (j&1) { + k = i; + ret = DoCommand(c, railtype, i, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + } + } + + /* signals too? */ + if (j & 3) { + // Can't build signals on a road. + if (IsTileType(c, MP_STREET)) return CMD_ERROR; + + if (flag & DC_EXEC) { + j = 4 - j; + do { + ret = DoCommand(c, k, 0, flag, CMD_BUILD_SIGNALS); + } while (--j); + } else { + ret = _price.build_signals; + } + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + } + } else if (p->mode == 3) { + //Clear stuff and then build single rail. + if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; + ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret + _price.build_rail; + + if (flag & DC_EXEC) { + DoCommand(c, railtype, p->attr&1, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_SINGLE_RAIL); + } + + goto clear_town_stuff; + } else { + // Unk + break; + } + + p++; + } + + if (!(flag & DC_EXEC)) { + if (t != NULL && rating > t->ratings[_current_player]) { + return CMD_ERROR; + } + } + + return total_cost; +} + +// Returns rule and cost +static int AiBuildDefaultRailTrack(TileIndex tile, byte p0, byte p1, byte p2, byte p3, byte dir, byte cargo, RailType railtype, int32* cost) +{ + int i; + const AiDefaultRailBlock *p; + + for (i = 0; (p = _default_rail_track_data[i]) != NULL; i++) { + if (p->p0 == p0 && p->p1 == p1 && p->p2 == p2 && p->p3 == p3 && + (p->dir == 0xFF || p->dir == dir || ((p->dir - 1) & 3) == dir)) { + *cost = AiDoBuildDefaultRailTrack(tile, p->data, railtype, DC_NO_TOWN_RATING); + if (!CmdFailed(*cost) && AiCheckTrackResources(tile, p->data, cargo)) + return i; + } + } + + return -1; +} + +static const byte _terraform_up_flags[] = { + 14, 13, 12, 11, + 10, 9, 8, 7, + 6, 5, 4, 3, + 2, 1, 0, 1, + 2, 1, 4, 1, + 2, 1, 8, 1, + 2, 1, 4, 2, + 2, 1 +}; + +static const byte _terraform_down_flags[] = { + 1, 2, 3, 4, + 5, 6, 1, 8, + 9, 10, 8, 12, + 4, 2, 0, 0, + 1, 2, 3, 4, + 5, 6, 2, 8, + 9, 10, 1, 12, + 8, 4 +}; + +static void AiDoTerraformLand(TileIndex tile, int dir, int unk, int mode) +{ + PlayerID old_player; + uint32 r; + Slope slope; + uint h; + + old_player = _current_player; + _current_player = OWNER_NONE; + + r = Random(); + + unk &= (int)r; + + do { + tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); + + r >>= 2; + if (r & 2) { + dir++; + if (r & 1) dir -= 2; + } + dir &= 3; + } while (--unk >= 0); + + slope = GetTileSlope(tile, &h); + + if (slope != SLOPE_FLAT) { + if (mode > 0 || (mode == 0 && !(r & 0xC))) { + // Terraform up + DoCommand(tile, _terraform_up_flags[slope - 1], 1, + DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); + } else if (h != 0) { + // Terraform down + DoCommand(tile, _terraform_down_flags[slope - 1], 0, + DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_TERRAFORM_LAND); + } + } + + _current_player = old_player; +} + +static void AiStateBuildDefaultRailBlocks(Player *p) +{ + uint i; + int j; + AiBuildRec *aib; + int rule; + int32 cost; + + // time out? + if (++p->ai.timeout_counter == 1388) { + p->ai.state = AIS_DELETE_RAIL_BLOCKS; + return; + } + + // do the following 8 times + for (i = 0; i < 8; i++) { + // check if we can build the default track + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + // this item has already been built? + if (aib->cur_building_rule != 255) continue; + + // adjust the coordinate randomly, + // to make sure that we find a position. + aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); + + // check if the track can be build there. + rule = AiBuildDefaultRailTrack(aib->use_tile, + p->ai.build_kind, p->ai.num_wagons, + aib->unk6, aib->unk7, + aib->direction, aib->cargo, + p->ai.railtype_to_use, + &cost + ); + + if (rule == -1) { + // cannot build, terraform after a while + if (p->ai.state_counter >= 600) { + AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); + } + // also try the other terraform direction + if (++p->ai.state_counter >= 1000) { + p->ai.state_counter = 0; + p->ai.state_mode = -p->ai.state_mode; + } + } else if (CheckPlayerHasMoney(cost)) { + int32 r; + // player has money, build it. + aib->cur_building_rule = rule; + + r = AiDoBuildDefaultRailTrack( + aib->use_tile, + _default_rail_track_data[rule]->data, + p->ai.railtype_to_use, + DC_EXEC | DC_NO_TOWN_RATING + ); + assert(!CmdFailed(r)); + } + } while (++aib,--j); + } + + // check if we're done with all of them + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + if (aib->cur_building_rule == 255) return; + } while (++aib,--j); + + // yep, all are done. switch state to the rail building state. + p->ai.state = AIS_BUILD_RAIL; + p->ai.state_mode = 255; +} + +static TileIndex AiGetEdgeOfDefaultRailBlock(byte rule, TileIndex tile, byte cmd, int *dir) +{ + const AiDefaultBlockData *p = _default_rail_track_data[rule]->data; + + while (p->mode != 3 || !((--cmd) & 0x80)) p++; + + return tile + ToTileIndexDiff(p->tileoffs) - TileOffsByDiagDir(*dir = p->attr); +} + +typedef struct AiRailPathFindData { + TileIndex tile; + TileIndex tile2; + int count; + bool flag; +} AiRailPathFindData; + +static bool AiEnumFollowTrack(TileIndex tile, AiRailPathFindData *a, int track, uint length, byte *state) +{ + if (a->flag) return true; + + if (length > 20 || tile == a->tile) { + a->flag = true; + return true; + } + + if (DistanceMax(tile, a->tile2) < 4) a->count++; + + return false; +} + +static bool AiDoFollowTrack(const Player* p) +{ + AiRailPathFindData arpfd; + + arpfd.tile = p->ai.start_tile_a; + arpfd.tile2 = p->ai.cur_tile_a; + arpfd.flag = false; + arpfd.count = 0; + FollowTrack(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a), 0x2000 | TRANSPORT_RAIL, p->ai.cur_dir_a^2, + (TPFEnumProc*)AiEnumFollowTrack, NULL, &arpfd); + return arpfd.count > 8; +} + +typedef struct AiRailFinder { + TileIndex final_tile; + byte final_dir; + byte depth; + byte recursive_mode; + byte cur_best_dir; + byte best_dir; + byte cur_best_depth; + byte best_depth; + uint cur_best_dist; + const byte *best_ptr; + uint best_dist; + TileIndex cur_best_tile, best_tile; + TileIndex bridge_end_tile; + Player *player; +} AiRailFinder; + +static const byte _ai_table_15[4][8] = { + {0, 0, 4, 3, 3, 1, 128 + 0, 64}, + {1, 1, 2, 0, 4, 2, 128 + 1, 65}, + {0, 2, 2, 3, 5, 1, 128 + 2, 66}, + {1, 3, 5, 0, 3, 2, 128 + 3, 67} +}; + +static const byte _dir_table_1[] = { 3, 9, 12, 6}; +static const byte _dir_table_2[] = {12, 6, 3, 9}; + + +static bool AiIsTileBanned(const Player* p, TileIndex tile, byte val) +{ + int i; + + for (i = 0; i != p->ai.banned_tile_count; i++) { + if (p->ai.banned_tiles[i] == tile && p->ai.banned_val[i] == val) { + return true; + } + } + return false; +} + +static void AiBanTile(Player* p, TileIndex tile, byte val) +{ + uint i; + + for (i = lengthof(p->ai.banned_tiles) - 1; i != 0; i--) { + p->ai.banned_tiles[i] = p->ai.banned_tiles[i - 1]; + p->ai.banned_val[i] = p->ai.banned_val[i - 1]; + } + + p->ai.banned_tiles[0] = tile; + p->ai.banned_val[0] = val; + + if (p->ai.banned_tile_count != lengthof(p->ai.banned_tiles)) { + p->ai.banned_tile_count++; + } +} + +static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, int dir); + +static bool AiCheckRailPathBetter(AiRailFinder *arf, const byte *p) +{ + bool better = false; + + if (arf->recursive_mode < 1) { + // Mode is 0. This means destination has not been found yet. + // If the found path is shorter than the current one, remember it. + if (arf->cur_best_dist < arf->best_dist) { + arf->best_dir = arf->cur_best_dir; + arf->best_dist = arf->cur_best_dist; + arf->best_ptr = p; + arf->best_tile = arf->cur_best_tile; + better = true; + } + } else if (arf->recursive_mode > 1) { + // Mode is 2. + if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { + arf->best_depth = arf->cur_best_depth; + arf->best_dist = 0; + arf->best_ptr = p; + arf->best_tile = 0; + better = true; + } + } + arf->recursive_mode = 0; + arf->cur_best_dist = (uint)-1; + arf->cur_best_depth = 0xff; + + return better; +} + +static inline void AiCheckBuildRailBridgeHere(AiRailFinder *arf, TileIndex tile, const byte *p) +{ + Slope tileh; + uint z; + bool flag; + + int dir2 = p[0] & 3; + + tileh = GetTileSlope(tile, &z); + if (tileh == _dir_table_1[dir2] || (tileh == SLOPE_FLAT && z != 0)) { + TileIndex tile_new = tile; + + // Allow bridges directly over bottom tiles + flag = z == 0; + for (;;) { + TileType type; + + if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! + tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); + type = GetTileType(tile_new); + + if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile_new, NULL) != SLOPE_FLAT) { + if (!flag) return; + break; + } + if (type != MP_WATER && type != MP_RAILWAY && type != MP_STREET) return; + flag = true; + } + + // Is building a (rail)bridge possible at this place (type doesn't matter)? + if (CmdFailed(DoCommand(tile_new, tile, 0 | arf->player->ai.railtype_to_use << 8, DC_AUTO, CMD_BUILD_BRIDGE))) { + return; + } + AiBuildRailRecursive(arf, tile_new, dir2); + + // At the bottom depth, check if the new path is better than the old one. + if (arf->depth == 1) { + if (AiCheckRailPathBetter(arf, p)) arf->bridge_end_tile = tile_new; + } + } +} + +static inline void AiCheckBuildRailTunnelHere(AiRailFinder *arf, TileIndex tile, const byte *p) +{ + uint z; + + if (GetTileSlope(tile, &z) == _dir_table_2[p[0] & 3] && z != 0) { + int32 cost = DoCommand(tile, arf->player->ai.railtype_to_use, 0, DC_AUTO, CMD_BUILD_TUNNEL); + + if (!CmdFailed(cost) && cost <= (arf->player->player_money>>4)) { + AiBuildRailRecursive(arf, _build_tunnel_endtile, p[0]&3); + if (arf->depth == 1) AiCheckRailPathBetter(arf, p); + } + } +} + + +static void AiBuildRailRecursive(AiRailFinder *arf, TileIndex tile, int dir) +{ + const byte *p; + + tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); + + // Reached destination? + if (tile == arf->final_tile) { + if (arf->final_dir != (dir^2)) { + if (arf->recursive_mode != 2) arf->recursive_mode = 1; + } else if (arf->recursive_mode != 2) { + arf->recursive_mode = 2; + arf->cur_best_depth = arf->depth; + } else { + if (arf->depth < arf->cur_best_depth) arf->cur_best_depth = arf->depth; + } + return; + } + + // Depth too deep? + if (arf->depth >= 4) { + uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); + + if (dist < arf->cur_best_dist) { + // Store the tile that is closest to the final position. + arf->cur_best_depth = arf->depth; + arf->cur_best_dist = dist; + arf->cur_best_tile = tile; + arf->cur_best_dir = dir; + } + return; + } + + // Increase recursion depth + arf->depth++; + + // Grab pointer to list of stuff that is possible to build + p = _ai_table_15[dir]; + + // Try to build a single rail in all directions. + if (GetTileZ(tile) == 0) { + p += 6; + } else { + do { + // Make sure the tile is not in the list of banned tiles and that a rail can be built here. + if (!AiIsTileBanned(arf->player, tile, p[0]) && + !CmdFailed(DoCommand(tile, arf->player->ai.railtype_to_use, p[0], DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL))) { + AiBuildRailRecursive(arf, tile, p[1]); + } + + // At the bottom depth? + if (arf->depth == 1) AiCheckRailPathBetter(arf, p); + + p += 2; + } while (!(p[0]&0x80)); + } + + AiCheckBuildRailBridgeHere(arf, tile, p); + AiCheckBuildRailTunnelHere(arf, tile, p+1); + + arf->depth--; +} + + +static const byte _dir_table_3[]= {0x25, 0x2A, 0x19, 0x16}; + +static void AiBuildRailConstruct(Player *p) +{ + AiRailFinder arf; + int i; + + // Check too much lookahead? + if (AiDoFollowTrack(p)) { + p->ai.state_counter = (Random()&0xE)+6; // Destruct this amount of blocks + p->ai.state_mode = 1; // Start destruct + + // Ban this tile and don't reach it for a while. + AiBanTile(p, p->ai.cur_tile_a, FindFirstBit(GetRailTrackStatus(p->ai.cur_tile_a))); + return; + } + + // Setup recursive finder and call it. + arf.player = p; + arf.final_tile = p->ai.cur_tile_b; + arf.final_dir = p->ai.cur_dir_b; + arf.depth = 0; + arf.recursive_mode = 0; + arf.best_ptr = NULL; + arf.cur_best_dist = (uint)-1; + arf.cur_best_depth = 0xff; + arf.best_dist = (uint)-1; + arf.best_depth = 0xff; + arf.cur_best_tile = 0; + arf.best_tile = 0; + AiBuildRailRecursive(&arf, p->ai.cur_tile_a, p->ai.cur_dir_a); + + // Reached destination? + if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { + p->ai.state_mode = 255; + return; + } + + // Didn't find anything to build? + if (arf.best_ptr == NULL) { + // Terraform some + for (i = 0; i != 5; i++) { + AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); + } + + if (++p->ai.state_counter == 21) { + p->ai.state_counter = 40; + p->ai.state_mode = 1; + + // Ban this tile + AiBanTile(p, p->ai.cur_tile_a, FindFirstBit(GetRailTrackStatus(p->ai.cur_tile_a))); + } + return; + } + + p->ai.cur_tile_a += TileOffsByDiagDir(p->ai.cur_dir_a); + + if (arf.best_ptr[0] & 0x80) { + int i; + int32 bridge_len = GetBridgeLength(arf.bridge_end_tile, p->ai.cur_tile_a); + + /* Figure out which (rail)bridge type to build + * start with best bridge, then go down to worse and worse bridges + * unnecessary to check for worst bridge (i=0), since AI will always build + * that. AI is so fucked up that fixing this small thing will probably not + * solve a thing + */ + for (i = MAX_BRIDGES - 1; i != 0; i--) { + if (CheckBridge_Stuff(i, bridge_len)) { + int32 cost = DoCommand(arf.bridge_end_tile, p->ai.cur_tile_a, i | (p->ai.railtype_to_use << 8), DC_AUTO, CMD_BUILD_BRIDGE); + if (!CmdFailed(cost) && cost < (p->player_money >> 5)) break; + } + } + + // Build it + DoCommand(arf.bridge_end_tile, p->ai.cur_tile_a, i | (p->ai.railtype_to_use << 8), DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); + + p->ai.cur_tile_a = arf.bridge_end_tile; + p->ai.state_counter = 0; + } else if (arf.best_ptr[0] & 0x40) { + // tunnel + DoCommand(p->ai.cur_tile_a, p->ai.railtype_to_use, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); + p->ai.cur_tile_a = _build_tunnel_endtile; + p->ai.state_counter = 0; + } else { + // rail + p->ai.cur_dir_a = arf.best_ptr[1]; + DoCommand(p->ai.cur_tile_a, p->ai.railtype_to_use, arf.best_ptr[0], + DC_EXEC | DC_AUTO | DC_NO_WATER | DC_NO_RAIL_OVERLAP, CMD_BUILD_SINGLE_RAIL); + p->ai.state_counter = 0; + } + + if (arf.best_tile != 0) { + for (i = 0; i != 2; i++) { + AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); + } + } +} + +static bool AiRemoveTileAndGoForward(Player *p) +{ + byte b; + int bit; + const byte *ptr; + TileIndex tile = p->ai.cur_tile_a; + TileIndex tilenew; + + if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if (IsTunnel(tile)) { + // Clear the tunnel and continue at the other side of it. + if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) + return false; + p->ai.cur_tile_a = TILE_MASK(_build_tunnel_endtile - TileOffsByDiagDir(p->ai.cur_dir_a)); + return true; + } else { + // Check if the bridge points in the right direction. + // This is not really needed the first place AiRemoveTileAndGoForward is called. + if (DiagDirToAxis(GetBridgeRampDirection(tile)) != (p->ai.cur_dir_a & 1U)) return false; + + tile = GetOtherBridgeEnd(tile); + + tilenew = TILE_MASK(tile - TileOffsByDiagDir(p->ai.cur_dir_a)); + // And clear the bridge. + if (CmdFailed(DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR))) + return false; + p->ai.cur_tile_a = tilenew; + return true; + } + } + + // Find the railtype at the position. Quit if no rail there. + b = GetRailTrackStatus(tile) & _dir_table_3[p->ai.cur_dir_a]; + if (b == 0) return false; + + // Convert into a bit position that CMD_REMOVE_SINGLE_RAIL expects. + bit = FindFirstBit(b); + + // Then remove and signals if there are any. + if (IsTileType(tile, MP_RAILWAY) && + GetRailTileType(tile) == RAIL_TILE_SIGNALS) { + DoCommand(tile, 0, 0, DC_EXEC, CMD_REMOVE_SIGNALS); + } + + // And also remove the rail. + if (CmdFailed(DoCommand(tile, 0, bit, DC_EXEC, CMD_REMOVE_SINGLE_RAIL))) + return false; + + // Find the direction at the other edge of the rail. + ptr = _ai_table_15[p->ai.cur_dir_a ^ 2]; + while (ptr[0] != bit) ptr += 2; + p->ai.cur_dir_a = ptr[1] ^ 2; + + // And then also switch tile. + p->ai.cur_tile_a = TILE_MASK(p->ai.cur_tile_a - TileOffsByDiagDir(p->ai.cur_dir_a)); + + return true; +} + + +static void AiBuildRailDestruct(Player *p) +{ + // Decrease timeout. + if (!--p->ai.state_counter) { + p->ai.state_mode = 2; + p->ai.state_counter = 0; + } + + // Don't do anything if the destination is already reached. + if (p->ai.cur_tile_a == p->ai.start_tile_a) return; + + AiRemoveTileAndGoForward(p); +} + + +static void AiBuildRail(Player *p) +{ + switch (p->ai.state_mode) { + case 0: // Construct mode, build new rail. + AiBuildRailConstruct(p); + break; + + case 1: // Destruct mode, destroy the rail currently built. + AiBuildRailDestruct(p); + break; + + case 2: { + uint i; + + // Terraform some and then try building again. + for (i = 0; i != 4; i++) { + AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); + } + + if (++p->ai.state_counter == 4) { + p->ai.state_counter = 0; + p->ai.state_mode = 0; + } + } + + default: break; + } +} + +static void AiStateBuildRail(Player *p) +{ + int num; + AiBuildRec *aib; + byte cmd; + TileIndex tile; + int dir; + + // time out? + if (++p->ai.timeout_counter == 1388) { + p->ai.state = AIS_DELETE_RAIL_BLOCKS; + return; + } + + // Currently building a rail between two points? + if (p->ai.state_mode != 255) { + AiBuildRail(p); + + // Alternate between edges + swap_tile(&p->ai.start_tile_a, &p->ai.start_tile_b); + swap_tile(&p->ai.cur_tile_a, &p->ai.cur_tile_b); + swap_byte(&p->ai.start_dir_a, &p->ai.start_dir_b); + swap_byte(&p->ai.cur_dir_a, &p->ai.cur_dir_b); + return; + } + + // Now, find two new points to build between + num = p->ai.num_build_rec; + aib = &p->ai.src; + + for (;;) { + cmd = aib->buildcmd_a; + aib->buildcmd_a = 255; + if (cmd != 255) break; + + cmd = aib->buildcmd_b; + aib->buildcmd_b = 255; + if (cmd != 255) break; + + aib++; + if (--num == 0) { + p->ai.state = AIS_BUILD_RAIL_VEH; + p->ai.state_counter = 0; // timeout + return; + } + } + + // Find first edge to build from. + tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, cmd&3, &dir); + p->ai.start_tile_a = tile; + p->ai.cur_tile_a = tile; + p->ai.start_dir_a = dir; + p->ai.cur_dir_a = dir; + DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir&1)?1:0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); + + assert(TILE_MASK(tile) != 0xFF00); + + // Find second edge to build to + aib = (&p->ai.src) + ((cmd >> 4)&0xF); + tile = AiGetEdgeOfDefaultRailBlock(aib->cur_building_rule, aib->use_tile, (cmd>>2)&3, &dir); + p->ai.start_tile_b = tile; + p->ai.cur_tile_b = tile; + p->ai.start_dir_b = dir; + p->ai.cur_dir_b = dir; + DoCommand(TILE_MASK(tile + TileOffsByDiagDir(dir)), 0, (dir&1)?1:0, DC_EXEC, CMD_REMOVE_SINGLE_RAIL); + + assert(TILE_MASK(tile) != 0xFF00); + + // And setup state. + p->ai.state_mode = 2; + p->ai.state_counter = 0; + p->ai.banned_tile_count = 0; +} + +static StationID AiGetStationIdByDef(TileIndex tile, int id) +{ + const AiDefaultBlockData *p = _default_rail_track_data[id]->data; + while (p->mode != 1) p++; + return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); +} + +static EngineID AiFindBestWagon(CargoID cargo, RailType railtype) +{ + EngineID best_veh_index = INVALID_ENGINE; + EngineID i; + uint16 best_capacity = 0; + uint16 best_speed = 0; + uint speed; + + for (i = 0; i < NUM_TRAIN_ENGINES; i++) { + const RailVehicleInfo *rvi = RailVehInfo(i); + const Engine* e = GetEngine(i); + + if (!IsCompatibleRail(e->railtype, railtype) || + !(rvi->flags & RVI_WAGON) || + !HASBIT(e->player_avail, _current_player)) { + continue; + } + + if (rvi->cargo_type != cargo) continue; + + /* max_speed of 0 indicates no speed limit */ + speed = rvi->max_speed == 0 ? 0xFFFF : rvi->max_speed; + + if (rvi->capacity >= best_capacity && speed >= best_speed) { + best_capacity = rvi->capacity; + best_speed = best_speed; + best_veh_index = i; + } + } + + return best_veh_index; +} + +static void AiStateBuildRailVeh(Player *p) +{ + const AiDefaultBlockData *ptr; + TileIndex tile; + EngineID veh; + int i; + CargoID cargo; + int32 cost; + Vehicle *v; + VehicleID loco_id; + + ptr = _default_rail_track_data[p->ai.src.cur_building_rule]->data; + while (ptr->mode != 0) ptr++; + + tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); + + + cargo = p->ai.cargo_type; + for (i = 0;;) { + if (p->ai.wagon_list[i] == INVALID_VEHICLE) { + veh = AiFindBestWagon(cargo, p->ai.railtype_to_use); + /* veh will return INVALID_ENGINE if no suitable wagon is available. + * We shall treat this in the same way as having no money */ + if (veh == INVALID_ENGINE) goto handle_nocash; + cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); + if (CmdFailed(cost)) goto handle_nocash; + p->ai.wagon_list[i] = _new_vehicle_id; + p->ai.wagon_list[i + 1] = INVALID_VEHICLE; + return; + } + if (cargo == CT_MAIL) cargo = CT_PASSENGERS; + if (++i == p->ai.num_wagons * 2 - 1) break; + } + + // Which locomotive to build? + veh = AiChooseTrainToBuild(p->ai.railtype_to_use, p->player_money, cargo != CT_PASSENGERS ? 1 : 0, tile); + if (veh == INVALID_ENGINE) { +handle_nocash: + // after a while, if AI still doesn't have cash, get out of this block by selling the wagons. + if (++p->ai.state_counter == 1000) { + for (i = 0; p->ai.wagon_list[i] != INVALID_VEHICLE; i++) { + cost = DoCommand(tile, p->ai.wagon_list[i], 0, DC_EXEC, CMD_SELL_RAIL_WAGON); + assert(!CmdFailed(cost)); + } + p->ai.state = AIS_0; + } + return; + } + + // Try to build the locomotive + cost = DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_RAIL_VEHICLE); + assert(!CmdFailed(cost)); + loco_id = _new_vehicle_id; + + // Sell a vehicle if the train is double headed. + v = GetVehicle(loco_id); + if (v->next != NULL) { + i = p->ai.wagon_list[p->ai.num_wagons * 2 - 2]; + p->ai.wagon_list[p->ai.num_wagons * 2 - 2] = INVALID_VEHICLE; + DoCommand(tile, i, 0, DC_EXEC, CMD_SELL_RAIL_WAGON); + } + + // Move the wagons onto the train + for (i = 0; p->ai.wagon_list[i] != INVALID_VEHICLE; i++) { + DoCommand(tile, p->ai.wagon_list[i] | (loco_id << 16), 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); + } + + for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { + const AiBuildRec* aib = &p->ai.src + p->ai.order_list_blocks[i]; + bool is_pass = ( + p->ai.cargo_type == CT_PASSENGERS || + p->ai.cargo_type == CT_MAIL || + (_opt.landscape == LT_NORMAL && p->ai.cargo_type == CT_VALUABLES) + ); + Order order; + + order.type = OT_GOTO_STATION; + order.flags = 0; + order.dest = AiGetStationIdByDef(aib->use_tile, aib->cur_building_rule); + + if (!is_pass && i == 1) order.flags |= OF_UNLOAD; + if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) + order.flags |= OF_FULL_LOAD; + + DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + } + + DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_TRAIN); + + DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + + if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; + + if (--p->ai.num_loco_to_build != 0) { +// p->ai.loco_id = INVALID_VEHICLE; + p->ai.wagon_list[0] = INVALID_VEHICLE; + } else { + p->ai.state = AIS_0; + } +} + +static void AiStateDeleteRailBlocks(Player *p) +{ + const AiBuildRec* aib = &p->ai.src; + uint num = p->ai.num_build_rec; + + do { + const AiDefaultBlockData* b; + + if (aib->cur_building_rule == 255) continue; + for (b = _default_rail_track_data[aib->cur_building_rule]->data; b->mode != 4; b++) { + DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + } + } while (++aib,--num); + + p->ai.state = AIS_0; +} + +static bool AiCheckRoadResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) +{ + uint values[NUM_CARGO]; + int rad; + + if (_patches.modified_catchment) { + rad = CA_TRUCK; // Same as CA_BUS at the moment? + } else { // change that at some point? + rad = 4; + } + + for (;; p++) { + if (p->mode == 4) { + return true; + } else if (p->mode == 1) { + TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); + + if (cargo & 0x80) { + GetProductionAroundTiles(values, tile2, 1, 1, rad); + return values[cargo & 0x7F] != 0; + } else { + GetAcceptanceAroundTiles(values, tile2, 1, 1, rad); + return (values[cargo]&~7) != 0; + } + } + } +} + +static bool _want_road_truck_station; +static int32 AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag); + +// Returns rule and cost +static int AiFindBestDefaultRoadBlock(TileIndex tile, byte direction, byte cargo, int32 *cost) +{ + int i; + const AiDefaultRoadBlock *p; + + _want_road_truck_station = (cargo & 0x7F) != CT_PASSENGERS; + + for (i = 0; (p = _road_default_block_data[i]) != NULL; i++) { + if (p->dir == direction) { + *cost = AiDoBuildDefaultRoadBlock(tile, p->data, 0); + if (!CmdFailed(*cost) && AiCheckRoadResources(tile, p->data, cargo)) + return i; + } + } + + return -1; +} + +static int32 AiDoBuildDefaultRoadBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag) +{ + int32 ret; + int32 total_cost = 0; + Town *t = NULL; + int rating = 0; + int roadflag = 0; + + for (;p->mode != 4;p++) { + TileIndex c = TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)); + + _cleared_town = NULL; + + if (p->mode == 2) { + if (IsTileType(c, MP_STREET) && + GetRoadTileType(c) == ROAD_TILE_NORMAL && + (GetRoadBits(c) & p->attr) != 0) { + roadflag |= 2; + + // all bits are already built? + if ((GetRoadBits(c) & p->attr) == p->attr) continue; + } + + ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + + continue; + } + + if (p->mode == 0) { + // Depot + ret = DoCommand(c, p->attr, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_DEPOT); + goto clear_town_stuff; + } else if (p->mode == 1) { + if (_want_road_truck_station) { + // Truck station + ret = DoCommand(c, p->attr, RS_TRUCK, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); + } else { + // Bus station + ret = DoCommand(c, p->attr, RS_BUS, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_BUILD_ROAD_STOP); + } +clear_town_stuff:; + + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + + if (_cleared_town != NULL) { + if (t != NULL && t != _cleared_town) return CMD_ERROR; + t = _cleared_town; + rating += _cleared_town_rating; + } + } else if (p->mode == 3) { + if (flag & DC_EXEC) continue; + + if (GetTileSlope(c, NULL) != SLOPE_FLAT) return CMD_ERROR; + + if (!IsTileType(c, MP_STREET) || GetRoadTileType(c) != ROAD_TILE_NORMAL) { + ret = DoCommand(c, 0, 0, flag | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, CMD_LANDSCAPE_CLEAR); + if (CmdFailed(ret)) return CMD_ERROR; + } + + } + } + + if (!_want_road_truck_station && !(roadflag & 2)) return CMD_ERROR; + + if (!(flag & DC_EXEC)) { + if (t != NULL && rating > t->ratings[_current_player]) return CMD_ERROR; + } + return total_cost; +} + +// Make sure the blocks are not too close to each other +static bool AiCheckBlockDistances(Player *p, TileIndex tile) +{ + const AiBuildRec* aib = &p->ai.src; + uint num = p->ai.num_build_rec; + + do { + if (aib->cur_building_rule != 255) { + if (DistanceManhattan(aib->use_tile, tile) < 9) return false; + } + } while (++aib, --num); + + return true; +} + + +static void AiStateBuildDefaultRoadBlocks(Player *p) +{ + uint i; + int j; + AiBuildRec *aib; + int rule; + int32 cost; + + // time out? + if (++p->ai.timeout_counter == 1388) { + p->ai.state = AIS_DELETE_RAIL_BLOCKS; + return; + } + + // do the following 8 times + for (i = 0; i != 8; i++) { + // check if we can build the default track + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + // this item has already been built? + if (aib->cur_building_rule != 255) continue; + + // adjust the coordinate randomly, + // to make sure that we find a position. + aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); + + // check if the road can be built there. + rule = AiFindBestDefaultRoadBlock( + aib->use_tile, aib->direction, aib->cargo, &cost + ); + + if (rule == -1) { + // cannot build, terraform after a while + if (p->ai.state_counter >= 600) { + AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); + } + // also try the other terraform direction + if (++p->ai.state_counter >= 1000) { + p->ai.state_counter = 0; + p->ai.state_mode = -p->ai.state_mode; + } + } else if (CheckPlayerHasMoney(cost) && AiCheckBlockDistances(p,aib->use_tile)) { + int32 r; + + // player has money, build it. + aib->cur_building_rule = rule; + + r = AiDoBuildDefaultRoadBlock( + aib->use_tile, + _road_default_block_data[rule]->data, + DC_EXEC | DC_NO_TOWN_RATING + ); + assert(!CmdFailed(r)); + } + } while (++aib,--j); + } + + // check if we're done with all of them + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + if (aib->cur_building_rule == 255) return; + } while (++aib,--j); + + // yep, all are done. switch state to the rail building state. + p->ai.state = AIS_BUILD_ROAD; + p->ai.state_mode = 255; +} + +typedef struct { + TileIndex final_tile; + byte final_dir; + byte depth; + byte recursive_mode; + byte cur_best_dir; + byte best_dir; + byte cur_best_depth; + byte best_depth; + uint cur_best_dist; + const byte *best_ptr; + uint best_dist; + TileIndex cur_best_tile, best_tile; + TileIndex bridge_end_tile; + Player *player; +} AiRoadFinder; + +typedef struct AiRoadEnum { + TileIndex dest; + TileIndex best_tile; + int best_track; + uint best_dist; +} AiRoadEnum; + +static const byte _dir_by_track[] = { + 0, 1, 0, 1, 2, 1, + 0, 0, + 2, 3, 3, 2, 3, 0, +}; + +static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, int dir); + +static bool AiCheckRoadPathBetter(AiRoadFinder *arf, const byte *p) +{ + bool better = false; + + if (arf->recursive_mode < 1) { + // Mode is 0. This means destination has not been found yet. + // If the found path is shorter than the current one, remember it. + if (arf->cur_best_dist < arf->best_dist || + (arf->cur_best_dist == arf->best_dist && arf->cur_best_depth < arf->best_depth)) { + arf->best_depth = arf->cur_best_depth; + arf->best_dist = arf->cur_best_dist; + arf->best_dir = arf->cur_best_dir; + arf->best_ptr = p; + arf->best_tile = arf->cur_best_tile; + better = true; + } + } else if (arf->recursive_mode > 1) { + // Mode is 2. + if (arf->best_dist != 0 || arf->cur_best_depth < arf->best_depth) { + arf->best_depth = arf->cur_best_depth; + arf->best_dist = 0; + arf->best_ptr = p; + arf->best_tile = 0; + better = true; + } + } + arf->recursive_mode = 0; + arf->cur_best_dist = (uint)-1; + arf->cur_best_depth = 0xff; + + return better; +} + + +static bool AiEnumFollowRoad(TileIndex tile, AiRoadEnum *a, int track, uint length, byte *state) +{ + uint dist = DistanceManhattan(tile, a->dest); + + if (dist <= a->best_dist) { + TileIndex tile2 = TILE_MASK(tile + TileOffsByDiagDir(_dir_by_track[track])); + + if (IsTileType(tile2, MP_STREET) && GetRoadTileType(tile2) == ROAD_TILE_NORMAL) { + a->best_dist = dist; + a->best_tile = tile; + a->best_track = track; + } + } + + return false; +} + +static const uint16 _ai_road_table_and[4] = { + 0x1009, + 0x16, + 0x520, + 0x2A00, +}; + +static bool AiCheckRoadFinished(Player *p) +{ + AiRoadEnum are; + TileIndex tile; + int dir = p->ai.cur_dir_a; + uint32 bits; + int i; + + are.dest = p->ai.cur_tile_b; + tile = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(dir)); + + if (IsRoadStopTile(tile) || IsTileDepotType(tile, TRANSPORT_ROAD)) return false; + bits = GetTileTrackStatus(tile, TRANSPORT_ROAD) & _ai_road_table_and[dir]; + if (bits == 0) return false; + + are.best_dist = (uint)-1; + + for_each_bit(i, bits) { + FollowTrack(tile, 0x3000 | TRANSPORT_ROAD, _dir_by_track[i], (TPFEnumProc*)AiEnumFollowRoad, NULL, &are); + } + + if (DistanceManhattan(tile, are.dest) <= are.best_dist) return false; + + if (are.best_dist == 0) return true; + + p->ai.cur_tile_a = are.best_tile; + p->ai.cur_dir_a = _dir_by_track[are.best_track]; + return false; +} + + +static bool AiBuildRoadHelper(TileIndex tile, int flags, int type) +{ + static const RoadBits _road_bits[] = { + ROAD_X, + ROAD_Y, + ROAD_NW | ROAD_NE, + ROAD_SW | ROAD_SE, + ROAD_NW | ROAD_SW, + ROAD_SE | ROAD_NE + }; + return !CmdFailed(DoCommand(tile, _road_bits[type], 0, flags, CMD_BUILD_ROAD)); +} + +static inline void AiCheckBuildRoadBridgeHere(AiRoadFinder *arf, TileIndex tile, const byte *p) +{ + Slope tileh; + uint z; + bool flag; + + int dir2 = p[0] & 3; + + tileh = GetTileSlope(tile, &z); + if (tileh == _dir_table_1[dir2] || (tileh == SLOPE_FLAT && z != 0)) { + TileIndex tile_new = tile; + + // Allow bridges directly over bottom tiles + flag = z == 0; + for (;;) { + TileType type; + + if ((TileIndexDiff)tile_new < -TileOffsByDiagDir(dir2)) return; // Wraping around map, no bridge possible! + tile_new = TILE_MASK(tile_new + TileOffsByDiagDir(dir2)); + type = GetTileType(tile_new); + + if (type == MP_CLEAR || type == MP_TREES || GetTileSlope(tile, NULL) != SLOPE_FLAT) { + // Allow a bridge if either we have a tile that's water, rail or street, + // or if we found an up tile. + if (!flag) return; + break; + } + if (type != MP_WATER && type != MP_RAILWAY && type != MP_STREET) return; + flag = true; + } + + // Is building a (rail)bridge possible at this place (type doesn't matter)? + if (CmdFailed(DoCommand(tile_new, tile, 0x8000, DC_AUTO, CMD_BUILD_BRIDGE))) + return; + AiBuildRoadRecursive(arf, tile_new, dir2); + + // At the bottom depth, check if the new path is better than the old one. + if (arf->depth == 1) { + if (AiCheckRoadPathBetter(arf, p)) arf->bridge_end_tile = tile_new; + } + } +} + +static inline void AiCheckBuildRoadTunnelHere(AiRoadFinder *arf, TileIndex tile, const byte *p) +{ + uint z; + + if (GetTileSlope(tile, &z) == _dir_table_2[p[0] & 3] && z != 0) { + int32 cost = DoCommand(tile, 0x200, 0, DC_AUTO, CMD_BUILD_TUNNEL); + + if (!CmdFailed(cost) && cost <= (arf->player->player_money>>4)) { + AiBuildRoadRecursive(arf, _build_tunnel_endtile, p[0]&3); + if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); + } + } +} + + + +static void AiBuildRoadRecursive(AiRoadFinder *arf, TileIndex tile, int dir) +{ + const byte *p; + + tile = TILE_MASK(tile + TileOffsByDiagDir(dir)); + + // Reached destination? + if (tile == arf->final_tile) { + if ((arf->final_dir^2) == dir) { + arf->recursive_mode = 2; + arf->cur_best_depth = arf->depth; + } + return; + } + + // Depth too deep? + if (arf->depth >= 4) { + uint dist = DistanceMaxPlusManhattan(tile, arf->final_tile); + if (dist < arf->cur_best_dist) { + // Store the tile that is closest to the final position. + arf->cur_best_dist = dist; + arf->cur_best_tile = tile; + arf->cur_best_dir = dir; + arf->cur_best_depth = arf->depth; + } + return; + } + + // Increase recursion depth + arf->depth++; + + // Grab pointer to list of stuff that is possible to build + p = _ai_table_15[dir]; + + // Try to build a single rail in all directions. + if (GetTileZ(tile) == 0) { + p += 6; + } else { + do { + // Make sure that a road can be built here. + if (AiBuildRoadHelper(tile, DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, p[0])) { + AiBuildRoadRecursive(arf, tile, p[1]); + } + + // At the bottom depth? + if (arf->depth == 1) AiCheckRoadPathBetter(arf, p); + + p += 2; + } while (!(p[0] & 0x80)); + } + + AiCheckBuildRoadBridgeHere(arf, tile, p); + AiCheckBuildRoadTunnelHere(arf, tile, p+1); + + arf->depth--; +} + + +static void AiBuildRoadConstruct(Player *p) +{ + AiRoadFinder arf; + int i; + TileIndex tile; + + // Reached destination? + if (AiCheckRoadFinished(p)) { + p->ai.state_mode = 255; + return; + } + + // Setup recursive finder and call it. + arf.player = p; + arf.final_tile = p->ai.cur_tile_b; + arf.final_dir = p->ai.cur_dir_b; + arf.depth = 0; + arf.recursive_mode = 0; + arf.best_ptr = NULL; + arf.cur_best_dist = (uint)-1; + arf.cur_best_depth = 0xff; + arf.best_dist = (uint)-1; + arf.best_depth = 0xff; + arf.cur_best_tile = 0; + arf.best_tile = 0; + AiBuildRoadRecursive(&arf, p->ai.cur_tile_a, p->ai.cur_dir_a); + + // Reached destination? + if (arf.recursive_mode == 2 && arf.cur_best_depth == 0) { + p->ai.state_mode = 255; + return; + } + + // Didn't find anything to build? + if (arf.best_ptr == NULL) { + // Terraform some +do_some_terraform: + for (i = 0; i != 5; i++) + AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); + + if (++p->ai.state_counter == 21) { + p->ai.state_mode = 1; + + p->ai.cur_tile_a = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a)); + p->ai.cur_dir_a ^= 2; + p->ai.state_counter = 0; + } + return; + } + + tile = TILE_MASK(p->ai.cur_tile_a + TileOffsByDiagDir(p->ai.cur_dir_a)); + + if (arf.best_ptr[0]&0x80) { + int i; + int32 bridge_len; + p->ai.cur_tile_a = arf.bridge_end_tile; + bridge_len = GetBridgeLength(tile, p->ai.cur_tile_a); // tile + + /* Figure out what (road)bridge type to build + * start with best bridge, then go down to worse and worse bridges + * unnecessary to check for worse bridge (i=0), since AI will always build that. + *AI is so fucked up that fixing this small thing will probably not solve a thing + */ + for (i = 10; i != 0; i--) { + if (CheckBridge_Stuff(i, bridge_len)) { + int32 cost = DoCommand(tile, p->ai.cur_tile_a, i + (0x80 << 8), DC_AUTO, CMD_BUILD_BRIDGE); + if (!CmdFailed(cost) && cost < (p->player_money >> 5)) break; + } + } + + // Build it + DoCommand(tile, p->ai.cur_tile_a, i + (0x80 << 8), DC_AUTO | DC_EXEC, CMD_BUILD_BRIDGE); + + p->ai.state_counter = 0; + } else if (arf.best_ptr[0]&0x40) { + // tunnel + DoCommand(tile, 0x200, 0, DC_AUTO | DC_EXEC, CMD_BUILD_TUNNEL); + p->ai.cur_tile_a = _build_tunnel_endtile; + p->ai.state_counter = 0; + } else { + // road + if (!AiBuildRoadHelper(tile, DC_EXEC | DC_AUTO | DC_NO_WATER | DC_AI_BUILDING, arf.best_ptr[0])) + goto do_some_terraform; + + p->ai.cur_dir_a = arf.best_ptr[1]; + p->ai.cur_tile_a = tile; + p->ai.state_counter = 0; + } + + if (arf.best_tile != 0) { + for (i = 0; i != 2; i++) + AiDoTerraformLand(arf.best_tile, arf.best_dir, 3, 0); + } +} + + +static void AiBuildRoad(Player *p) +{ + if (p->ai.state_mode < 1) { + // Construct mode, build new road. + AiBuildRoadConstruct(p); + } else if (p->ai.state_mode == 1) { + // Destruct mode, not implemented for roads. + p->ai.state_mode = 2; + p->ai.state_counter = 0; + } else if (p->ai.state_mode == 2) { + uint i; + + // Terraform some and then try building again. + for (i = 0; i != 4; i++) { + AiDoTerraformLand(p->ai.cur_tile_a, p->ai.cur_dir_a, 3, 0); + } + + if (++p->ai.state_counter == 4) { + p->ai.state_counter = 0; + p->ai.state_mode = 0; + } + } +} + +static TileIndex AiGetRoadBlockEdge(byte rule, TileIndex tile, int *dir) +{ + const AiDefaultBlockData *p = _road_default_block_data[rule]->data; + while (p->mode != 1) p++; + *dir = p->attr; + return TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); +} + + +static void AiStateBuildRoad(Player *p) +{ + int num; + AiBuildRec *aib; + byte cmd; + TileIndex tile; + int dir; + + // time out? + if (++p->ai.timeout_counter == 1388) { + p->ai.state = AIS_DELETE_ROAD_BLOCKS; + return; + } + + // Currently building a road between two points? + if (p->ai.state_mode != 255) { + AiBuildRoad(p); + + // Alternate between edges + swap_tile(&p->ai.start_tile_a, &p->ai.start_tile_b); + swap_tile(&p->ai.cur_tile_a, &p->ai.cur_tile_b); + swap_byte(&p->ai.start_dir_a, &p->ai.start_dir_b); + swap_byte(&p->ai.cur_dir_a, &p->ai.cur_dir_b); + + return; + } + + // Now, find two new points to build between + num = p->ai.num_build_rec; + aib = &p->ai.src; + + for (;;) { + cmd = aib->buildcmd_a; + aib->buildcmd_a = 255; + if (cmd != 255) break; + + aib++; + if (--num == 0) { + p->ai.state = AIS_BUILD_ROAD_VEHICLES; + return; + } + } + + // Find first edge to build from. + tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); + p->ai.start_tile_a = tile; + p->ai.cur_tile_a = tile; + p->ai.start_dir_a = dir; + p->ai.cur_dir_a = dir; + + // Find second edge to build to + aib = (&p->ai.src) + (cmd&0xF); + tile = AiGetRoadBlockEdge(aib->cur_building_rule, aib->use_tile, &dir); + p->ai.start_tile_b = tile; + p->ai.cur_tile_b = tile; + p->ai.start_dir_b = dir; + p->ai.cur_dir_b = dir; + + // And setup state. + p->ai.state_mode = 2; + p->ai.state_counter = 0; + p->ai.banned_tile_count = 0; +} + +static StationID AiGetStationIdFromRoadBlock(TileIndex tile, int id) +{ + const AiDefaultBlockData *p = _road_default_block_data[id]->data; + while (p->mode != 1) p++; + return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); +} + +static void AiStateBuildRoadVehicles(Player *p) +{ + const AiDefaultBlockData *ptr; + TileIndex tile; + VehicleID loco_id; + EngineID veh; + uint i; + + ptr = _road_default_block_data[p->ai.src.cur_building_rule]->data; + for (; ptr->mode != 0; ptr++) {} + tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); + + veh = AiChooseRoadVehToBuild(p->ai.cargo_type, p->player_money, tile); + if (veh == INVALID_ENGINE) { + p->ai.state = AIS_0; + return; + } + + if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_ROAD_VEH))) return; + + loco_id = _new_vehicle_id; + + if (GetVehicle(loco_id)->cargo_type != p->ai.cargo_type) { + /* Cargo type doesn't match, so refit it */ + if (CmdFailed(DoCommand(tile, loco_id, p->ai.cargo_type, DC_EXEC, CMD_REFIT_ROAD_VEH))) { + /* Refit failed... sell the vehicle */ + DoCommand(tile, loco_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); + return; + } + } + + for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { + const AiBuildRec* aib = &p->ai.src + p->ai.order_list_blocks[i]; + bool is_pass = ( + p->ai.cargo_type == CT_PASSENGERS || + p->ai.cargo_type == CT_MAIL || + (_opt.landscape == LT_NORMAL && p->ai.cargo_type == CT_VALUABLES) + ); + Order order; + + order.type = OT_GOTO_STATION; + order.flags = 0; + order.dest = AiGetStationIdFromRoadBlock(aib->use_tile, aib->cur_building_rule); + + if (!is_pass && i == 1) order.flags |= OF_UNLOAD; + if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) + order.flags |= OF_FULL_LOAD; + + DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + } + + DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_ROADVEH); + DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + + if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; + if (--p->ai.num_loco_to_build == 0) p->ai.state = AIS_0; +} + +static void AiStateDeleteRoadBlocks(Player *p) +{ + const AiBuildRec* aib = &p->ai.src; + uint num = p->ai.num_build_rec; + + do { + const AiDefaultBlockData* b; + + if (aib->cur_building_rule == 255) continue; + for (b = _road_default_block_data[aib->cur_building_rule]->data; b->mode != 4; b++) { + if (b->mode > 1) continue; + DoCommand(TILE_ADD(aib->use_tile, ToTileIndexDiff(b->tileoffs)), 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + } + } while (++aib,--num); + + p->ai.state = AIS_0; +} + + +static void AiStateAirportStuff(Player *p) +{ + const Station* st; + byte acc_planes; + int i; + AiBuildRec *aib; + byte rule; + + // Here we look for an airport we could use instead of building a new + // one. If we find such an aiport for any waypoint, + // AiStateBuildDefaultAirportBlocks() will kindly skip that one when + // building the waypoints. + + i = 0; + do { + // We do this all twice - once for the source (town in the case + // of oilrig route) and then for the destination (oilrig in the + // case of oilrig route). + aib = &p->ai.src + i; + + FOR_ALL_STATIONS(st) { + // Is this an airport? + if (!(st->facilities & FACIL_AIRPORT)) continue; + + // Do we own the airport? (Oilrigs aren't owned, though.) + if (st->owner != OWNER_NONE && st->owner != _current_player) continue; + + acc_planes = GetAirport(st->airport_type)->acc_planes; + + // Dismiss heliports, unless we are checking an oilrig. + if (acc_planes == HELICOPTERS_ONLY && (p->ai.build_kind != 1 || i != 1)) + continue; + + // Dismiss country airports if we are doing the other + // endpoint of an oilrig route. + if (acc_planes == AIRCRAFT_ONLY && (p->ai.build_kind == 1 && i == 0)) + continue; + + // Dismiss airports too far away. + if (DistanceMax(st->airport_tile, aib->spec_tile) > aib->rand_rng) + continue; + + // It's ideal airport, let's take it! + + /* XXX: This part is utterly broken - rule should + * contain number of the rule appropriate for the + * airport type (country, town, ...), see + * _airport_default_block_data (rule is just an index + * in this array). But the only difference between the + * currently existing two rules (rule 0 - town and rule + * 1 - country) is the attr field which is used only + * when building new airports - and that's irrelevant + * for us. So using just about any rule will suffice + * here for now (some of the new airport types would be + * broken because they will probably need different + * tileoff values etc), no matter that + * IsHangarTile() makes no sense. --pasky */ + if (acc_planes == HELICOPTERS_ONLY) { + /* Heliports should have maybe own rulesets but + * OTOH we don't want AI to pick them up when + * looking for a suitable airport type to build. + * So any of rules 0 or 1 would do for now. The + * original rule number was 2 but that's a bug + * because we have no such rule. */ + rule = 1; + } else { + rule = IsHangarTile(st->airport_tile); + } + + aib->cur_building_rule = rule; + aib->use_tile = st->airport_tile; + break; + } + } while (++i != p->ai.num_build_rec); + + p->ai.state = AIS_BUILD_DEFAULT_AIRPORT_BLOCKS; + p->ai.state_mode = 255; + p->ai.state_counter = 0; +} + +static int32 AiDoBuildDefaultAirportBlock(TileIndex tile, const AiDefaultBlockData *p, byte flag) +{ + int32 total_cost = 0, ret; + + for (; p->mode == 0; p++) { + if (!HASBIT(_avail_aircraft, p->attr)) return CMD_ERROR; + ret = DoCommand(TILE_MASK(tile + ToTileIndexDiff(p->tileoffs)), p->attr,0,flag | DC_AUTO | DC_NO_WATER,CMD_BUILD_AIRPORT); + if (CmdFailed(ret)) return CMD_ERROR; + total_cost += ret; + } + + return total_cost; +} + +static bool AiCheckAirportResources(TileIndex tile, const AiDefaultBlockData *p, byte cargo) +{ + uint values[NUM_CARGO]; + int rad; + + if (_patches.modified_catchment) { + rad = CA_AIR_LARGE; // I Have NFI what airport the + } else { // AI is going to build here + rad = 4; + } + + for (; p->mode == 0; p++) { + TileIndex tile2 = TILE_ADD(tile, ToTileIndexDiff(p->tileoffs)); + const AirportFTAClass* airport = GetAirport(p->attr); + uint w = airport->size_x; + uint h = airport->size_y; + + if (cargo & 0x80) { + GetProductionAroundTiles(values, tile2, w, h, rad); + return values[cargo & 0x7F] != 0; + } else { + GetAcceptanceAroundTiles(values, tile2, w, h, rad); + return values[cargo] >= 8; + } + } + return true; +} + +static int AiFindBestDefaultAirportBlock(TileIndex tile, byte cargo, byte heli, int32 *cost) +{ + const AiDefaultBlockData *p; + uint i; + + for (i = 0; (p = _airport_default_block_data[i]) != NULL; i++) { + // If we are doing a helicopter service, avoid building + // airports where they can't land. + if (heli && GetAirport(p->attr)->acc_planes == AIRCRAFT_ONLY) continue; + + *cost = AiDoBuildDefaultAirportBlock(tile, p, 0); + if (!CmdFailed(*cost) && AiCheckAirportResources(tile, p, cargo)) + return i; + } + return -1; +} + +static void AiStateBuildDefaultAirportBlocks(Player *p) +{ + int i, j; + AiBuildRec *aib; + int rule; + int32 cost; + + // time out? + if (++p->ai.timeout_counter == 1388) { + p->ai.state = AIS_0; + return; + } + + // do the following 8 times + i = 8; + do { + // check if we can build the default + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + // this item has already been built? + if (aib->cur_building_rule != 255) continue; + + // adjust the coordinate randomly, + // to make sure that we find a position. + aib->use_tile = AdjustTileCoordRandomly(aib->spec_tile, aib->rand_rng); + + // check if the aircraft stuff can be built there. + rule = AiFindBestDefaultAirportBlock(aib->use_tile, aib->cargo, p->ai.build_kind, &cost); + +// SetRedErrorSquare(aib->use_tile); + + if (rule == -1) { + // cannot build, terraform after a while + if (p->ai.state_counter >= 600) { + AiDoTerraformLand(aib->use_tile, Random()&3, 3, (int8)p->ai.state_mode); + } + // also try the other terraform direction + if (++p->ai.state_counter >= 1000) { + p->ai.state_counter = 0; + p->ai.state_mode = -p->ai.state_mode; + } + } else if (CheckPlayerHasMoney(cost) && AiCheckBlockDistances(p,aib->use_tile)) { + // player has money, build it. + int32 r; + + aib->cur_building_rule = rule; + + r = AiDoBuildDefaultAirportBlock( + aib->use_tile, + _airport_default_block_data[rule], + DC_EXEC | DC_NO_TOWN_RATING + ); + assert(!CmdFailed(r)); + } + } while (++aib,--j); + } while (--i); + + // check if we're done with all of them + aib = &p->ai.src; + j = p->ai.num_build_rec; + do { + if (aib->cur_building_rule == 255) return; + } while (++aib,--j); + + // yep, all are done. switch state. + p->ai.state = AIS_BUILD_AIRCRAFT_VEHICLES; +} + +static StationID AiGetStationIdFromAircraftBlock(TileIndex tile, int id) +{ + const AiDefaultBlockData *p = _airport_default_block_data[id]; + while (p->mode != 1) p++; + return GetStationIndex(TILE_ADD(tile, ToTileIndexDiff(p->tileoffs))); +} + +static void AiStateBuildAircraftVehicles(Player *p) +{ + const AiDefaultBlockData *ptr; + TileIndex tile; + EngineID veh; + int i; + VehicleID loco_id; + + ptr = _airport_default_block_data[p->ai.src.cur_building_rule]; + for (; ptr->mode != 0; ptr++) {} + + tile = TILE_ADD(p->ai.src.use_tile, ToTileIndexDiff(ptr->tileoffs)); + + veh = AiChooseAircraftToBuild(p->player_money, p->ai.build_kind != 0 ? 0 : AIR_CTOL); + if (veh == INVALID_ENGINE) return; + + /* XXX - Have the AI pick the hangar terminal in an airport. Eg get airport-type + * and offset to the FIRST depot because the AI picks the st->xy tile */ + tile += ToTileIndexDiff(GetAirport(GetStationByTile(tile)->airport_type)->airport_depots[0]); + if (CmdFailed(DoCommand(tile, veh, 0, DC_EXEC, CMD_BUILD_AIRCRAFT))) return; + loco_id = _new_vehicle_id; + + for (i = 0; p->ai.order_list_blocks[i] != 0xFF; i++) { + AiBuildRec *aib = (&p->ai.src) + p->ai.order_list_blocks[i]; + bool is_pass = (p->ai.cargo_type == CT_PASSENGERS || p->ai.cargo_type == CT_MAIL); + Order order; + + order.type = OT_GOTO_STATION; + order.flags = 0; + order.dest = AiGetStationIdFromAircraftBlock(aib->use_tile, aib->cur_building_rule); + + if (!is_pass && i == 1) order.flags |= OF_UNLOAD; + if (p->ai.num_want_fullload != 0 && (is_pass || i == 0)) + order.flags |= OF_FULL_LOAD; + + DoCommand(0, loco_id + (i << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + } + + DoCommand(0, loco_id, 0, DC_EXEC, CMD_START_STOP_AIRCRAFT); + + DoCommand(0, loco_id, _ai_service_interval, DC_EXEC, CMD_CHANGE_SERVICE_INT); + + if (p->ai.num_want_fullload != 0) p->ai.num_want_fullload--; + + if (--p->ai.num_loco_to_build == 0) p->ai.state = AIS_0; +} + +static void AiStateCheckShipStuff(Player *p) +{ + // XXX + error("!AiStateCheckShipStuff"); +} + +static void AiStateBuildDefaultShipBlocks(Player *p) +{ + // XXX + error("!AiStateBuildDefaultShipBlocks"); +} + +static void AiStateDoShipStuff(Player *p) +{ + // XXX + error("!AiStateDoShipStuff"); +} + +static void AiStateSellVeh(Player *p) +{ + Vehicle *v = p->ai.cur_veh; + + if (v->owner == _current_player) { + if (v->type == VEH_Train) { + + if (!IsTileDepotType(v->tile, TRANSPORT_RAIL) || v->u.rail.track != 0x80 || !(v->vehstatus&VS_STOPPED)) { + if (v->current_order.type != OT_GOTO_DEPOT) + DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_TRAIN_TO_DEPOT); + goto going_to_depot; + } + + // Sell whole train + DoCommand(v->tile, v->index, 1, DC_EXEC, CMD_SELL_RAIL_WAGON); + + } else if (v->type == VEH_Road) { + if (!IsRoadVehInDepotStopped(v)) { + if (v->current_order.type != OT_GOTO_DEPOT) + DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); + goto going_to_depot; + } + + DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); + } else if (v->type == VEH_Aircraft) { + if (!IsAircraftInHangarStopped(v)) { + if (v->current_order.type != OT_GOTO_DEPOT) + DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_AIRCRAFT_TO_HANGAR); + goto going_to_depot; + } + + DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_AIRCRAFT); + } else if (v->type == VEH_Ship) { + // XXX: not implemented + error("!v->type == VEH_Ship"); + } + } + + goto return_to_loop; +going_to_depot:; + if (++p->ai.state_counter <= 832) return; + + if (v->current_order.type == OT_GOTO_DEPOT) { + v->current_order.type = OT_DUMMY; + v->current_order.flags = 0; + InvalidateWindow(WC_VEHICLE_VIEW, v->index); + } +return_to_loop:; + p->ai.state = AIS_VEH_LOOP; +} + +static void AiStateRemoveStation(Player *p) +{ + // Remove stations that aren't in use by any vehicle + byte *in_use; + const Order *ord; + const Station *st; + TileIndex tile; + + // Go to this state when we're done. + p->ai.state = AIS_1; + + // Get a list of all stations that are in use by a vehicle + in_use = malloc(GetMaxStationIndex() + 1); + memset(in_use, 0, GetMaxStationIndex() + 1); + FOR_ALL_ORDERS(ord) { + if (ord->type == OT_GOTO_STATION) in_use[ord->dest] = 1; + } + + // Go through all stations and delete those that aren't in use + FOR_ALL_STATIONS(st) { + if (st->owner == _current_player && !in_use[st->index] && + ( (st->bus_stops != NULL && (tile = st->bus_stops->xy) != 0) || + (st->truck_stops != NULL && (tile = st->truck_stops->xy)) != 0 || + (tile = st->train_tile) != 0 || + (tile = st->dock_tile) != 0 || + (tile = st->airport_tile) != 0)) { + DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + } + } + + free(in_use); +} + +static void AiRemovePlayerRailOrRoad(Player *p, TileIndex tile) +{ + TrackBits rails; + + if (IsTileType(tile, MP_RAILWAY)) { + if (!IsTileOwner(tile, _current_player)) return; + + if (IsPlainRailTile(tile)) { +is_rail_crossing:; + rails = GetRailTrackStatus(tile); + + if (rails == TRACK_BIT_HORZ || rails == TRACK_BIT_VERT) return; + + if (rails & TRACK_BIT_3WAY_NE) { +pos_0: + if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(1, 0))) & TRACK_BIT_3WAY_SW) == 0) { + p->ai.cur_dir_a = 0; + p->ai.cur_tile_a = tile; + p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; + return; + } + } + + if (rails & TRACK_BIT_3WAY_SE) { +pos_1: + if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(0, 1))) & TRACK_BIT_3WAY_NW) == 0) { + p->ai.cur_dir_a = 1; + p->ai.cur_tile_a = tile; + p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; + return; + } + } + + if (rails & TRACK_BIT_3WAY_SW) { +pos_2: + if ((GetRailTrackStatus(TILE_MASK(tile + TileDiffXY(1, 0))) & TRACK_BIT_3WAY_NE) == 0) { + p->ai.cur_dir_a = 2; + p->ai.cur_tile_a = tile; + p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; + return; + } + } + + if (rails & TRACK_BIT_3WAY_NW) { +pos_3: + if ((GetRailTrackStatus(TILE_MASK(tile - TileDiffXY(0, 1))) & TRACK_BIT_3WAY_SE) == 0) { + p->ai.cur_dir_a = 3; + p->ai.cur_tile_a = tile; + p->ai.state = AIS_REMOVE_SINGLE_RAIL_TILE; + return; + } + } + } else { + static const byte _depot_bits[] = {0x19,0x16,0x25,0x2A}; + + DiagDirection dir = GetRailDepotDirection(tile); + + if (GetRailTrackStatus(tile + TileOffsByDiagDir(dir)) & _depot_bits[dir]) + return; + + DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + } + } else if (IsTileType(tile, MP_STREET)) { + if (!IsTileOwner(tile, _current_player)) return; + + if (IsLevelCrossing(tile)) goto is_rail_crossing; + + if (GetRoadTileType(tile) == ROAD_TILE_DEPOT) { + DiagDirection dir; + TileIndex t; + + // Check if there are any stations around. + t = tile + TileDiffXY(-1, 0); + if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; + + t = tile + TileDiffXY(1, 0); + if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; + + t = tile + TileDiffXY(0, -1); + if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; + + t = tile + TileDiffXY(0, 1); + if (IsTileType(t, MP_STATION) && IsTileOwner(t, _current_player)) return; + + dir = GetRoadDepotDirection(tile); + + DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + DoCommand( + TILE_MASK(tile + TileOffsByDiagDir(dir)), + DiagDirToRoadBits(ReverseDiagDir(dir)), + 0, + DC_EXEC, + CMD_REMOVE_ROAD); + } + } else if (IsTileType(tile, MP_TUNNELBRIDGE)) { + if (!IsTileOwner(tile, _current_player) || + !IsBridge(tile) || + GetBridgeTransportType(tile) != TRANSPORT_RAIL) { + return; + } + + rails = 0; + + switch (GetBridgeRampDirection(tile)) { + default: + case DIAGDIR_NE: goto pos_2; + case DIAGDIR_SE: goto pos_3; + case DIAGDIR_SW: goto pos_0; + case DIAGDIR_NW: goto pos_1; + } + } +} + +static void AiStateRemoveTrack(Player *p) +{ + /* Was 1000 for standard 8x8 maps. */ + int num = MapSizeX() * 4; + + do { + TileIndex tile = ++p->ai.state_counter; + + // Iterated all tiles? + if (tile >= MapSize()) { + p->ai.state = AIS_REMOVE_STATION; + return; + } + + // Remove player stuff in that tile + AiRemovePlayerRailOrRoad(p, tile); + if (p->ai.state != AIS_REMOVE_TRACK) return; + } while (--num); +} + +static void AiStateRemoveSingleRailTile(Player *p) +{ + // Remove until we can't remove more. + if (!AiRemoveTileAndGoForward(p)) p->ai.state = AIS_REMOVE_TRACK; +} + +static AiStateAction * const _ai_actions[] = { + AiCase0, + AiCase1, + AiStateVehLoop, + AiStateCheckReplaceVehicle, + AiStateDoReplaceVehicle, + AiStateWantNewRoute, + + AiStateBuildDefaultRailBlocks, + AiStateBuildRail, + AiStateBuildRailVeh, + AiStateDeleteRailBlocks, + + AiStateBuildDefaultRoadBlocks, + AiStateBuildRoad, + AiStateBuildRoadVehicles, + AiStateDeleteRoadBlocks, + + AiStateAirportStuff, + AiStateBuildDefaultAirportBlocks, + AiStateBuildAircraftVehicles, + + AiStateCheckShipStuff, + AiStateBuildDefaultShipBlocks, + AiStateDoShipStuff, + + AiStateSellVeh, + AiStateRemoveStation, + AiStateRemoveTrack, + + AiStateRemoveSingleRailTile +}; + +extern void ShowBuyCompanyDialog(uint player); + +static void AiHandleTakeover(Player *p) +{ + if (p->bankrupt_timeout != 0) { + p->bankrupt_timeout -= 8; + if (p->bankrupt_timeout > 0) return; + p->bankrupt_timeout = 0; + DeleteWindowById(WC_BUY_COMPANY, _current_player); + if (IsLocalPlayer()) { + AskExitToGameMenu(); + return; + } + if (IsHumanPlayer(_current_player)) return; + } + + if (p->bankrupt_asked == 255) return; + + { + uint asked = p->bankrupt_asked; + Player *pp, *best_pl = NULL; + int32 best_val = -1; + uint old_p; + + // Ask the guy with the highest performance hist. + FOR_ALL_PLAYERS(pp) { + if (pp->is_active && + !(asked&1) && + pp->bankrupt_asked == 0 && + best_val < pp->old_economy[1].performance_history) { + best_val = pp->old_economy[1].performance_history; + best_pl = pp; + } + asked>>=1; + } + + // Asked all players? + if (best_val == -1) { + p->bankrupt_asked = 255; + return; + } + + SETBIT(p->bankrupt_asked, best_pl->index); + + if (best_pl->index == _local_player) { + p->bankrupt_timeout = 4440; + ShowBuyCompanyDialog(_current_player); + return; + } + if (IsHumanPlayer(best_pl->index)) return; + + // Too little money for computer to buy it? + if (best_pl->player_money >> 1 >= p->bankrupt_value) { + // Computer wants to buy it. + old_p = _current_player; + _current_player = p->index; + DoCommand(0, old_p, 0, DC_EXEC, CMD_BUY_COMPANY); + _current_player = old_p; + } + } +} + +static void AiAdjustLoan(const Player* p) +{ + int32 base = AiGetBasePrice(p); + + if (p->player_money > base * 1400) { + // Decrease loan + if (p->current_loan != 0) { + DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); + } + } else if (p->player_money < base * 500) { + // Increase loan + if (p->current_loan < _economy.max_loan && + p->num_valid_stat_ent >= 2 && + -(p->old_economy[0].expenses+p->old_economy[1].expenses) < base * 60) { + DoCommand(0, 0, 0, DC_EXEC, CMD_INCREASE_LOAN); + } + } +} + +static void AiBuildCompanyHQ(Player *p) +{ + TileIndex tile; + + if (p->location_of_house == 0 && + p->last_build_coordinate != 0) { + tile = AdjustTileCoordRandomly(p->last_build_coordinate, 8); + DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); + } +} + + +void AiDoGameLoop(Player *p) +{ + if (p->bankrupt_asked != 0) { + AiHandleTakeover(p); + return; + } + + // Ugly hack to make sure the service interval of the AI is good, not looking + // to the patch-setting + // Also, it takes into account the setting if the service-interval is in days + // or in % + _ai_service_interval = _patches.servint_ispercent?80:180; + + if (IsHumanPlayer(_current_player)) return; + + AiAdjustLoan(p); + AiBuildCompanyHQ(p); + +#if 0 + { + static byte old_state = 99; + static bool hasdots = false; + char *_ai_state_names[]={ + "AiCase0", + "AiCase1", + "AiStateVehLoop", + "AiStateCheckReplaceVehicle", + "AiStateDoReplaceVehicle", + "AiStateWantNewRoute", + "AiStateBuildDefaultRailBlocks", + "AiStateBuildRail", + "AiStateBuildRailVeh", + "AiStateDeleteRailBlocks", + "AiStateBuildDefaultRoadBlocks", + "AiStateBuildRoad", + "AiStateBuildRoadVehicles", + "AiStateDeleteRoadBlocks", + "AiStateAirportStuff", + "AiStateBuildDefaultAirportBlocks", + "AiStateBuildAircraftVehicles", + "AiStateCheckShipStuff", + "AiStateBuildDefaultShipBlocks", + "AiStateDoShipStuff", + "AiStateSellVeh", + "AiStateRemoveStation", + "AiStateRemoveTrack", + "AiStateRemoveSingleRailTile" + }; + + if (p->ai.state != old_state) { + if (hasdots) + printf("\n"); + hasdots=false; + printf("AiState: %s\n", _ai_state_names[old_state=p->ai.state]); + } else { + printf("."); + hasdots=true; + } + } +#endif + + _ai_actions[p->ai.state](p); +} diff --git a/src/ai/trolly/build.c b/src/ai/trolly/build.c deleted file mode 100644 --- a/src/ai/trolly/build.c +++ /dev/null @@ -1,324 +0,0 @@ -/* $Id$ */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../functions.h" -#include "../../map.h" -#include "../../road_map.h" -#include "../../tile.h" -#include "../../vehicle.h" -#include "../../command.h" -#include "trolly.h" -#include "../../engine.h" -#include "../../station.h" -#include "../../variables.h" -#include "../../bridge.h" -#include "../ai.h" - -// Build HQ -// Params: -// tile : tile where HQ is going to be build -bool AiNew_Build_CompanyHQ(Player *p, TileIndex tile) -{ - if (CmdFailed(AI_DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ))) - return false; - AI_DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); - return true; -} - - -// Build station -// Params: -// type : AI_TRAIN/AI_BUS/AI_TRUCK : indicates the type of station -// tile : tile where station is going to be build -// length : in case of AI_TRAIN: length of station -// numtracks : in case of AI_TRAIN: tracks of station -// direction : the direction of the station -// flag : flag passed to DoCommand (normally 0 to get the cost or DC_EXEC to build it) -int AiNew_Build_Station(Player *p, byte type, TileIndex tile, byte length, byte numtracks, byte direction, byte flag) -{ - if (type == AI_TRAIN) - return AI_DoCommand(tile, direction + (numtracks << 8) + (length << 16), 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_RAILROAD_STATION); - - if (type == AI_BUS) - return AI_DoCommand(tile, direction, RS_BUS, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); - - return AI_DoCommand(tile, direction, RS_TRUCK, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); -} - - -// Builds a brdige. The second best out of the ones available for this player -// Params: -// tile_a : starting point -// tile_b : end point -// flag : flag passed to DoCommand -int AiNew_Build_Bridge(Player *p, TileIndex tile_a, TileIndex tile_b, byte flag) -{ - int bridge_type, bridge_len, type, type2; - - // Find a good bridgetype (the best money can buy) - bridge_len = GetBridgeLength(tile_a, tile_b); - type = type2 = 0; - for (bridge_type = MAX_BRIDGES-1; bridge_type >= 0; bridge_type--) { - if (CheckBridge_Stuff(bridge_type, bridge_len)) { - type2 = type; - type = bridge_type; - // We found two bridges, exit - if (type2 != 0) break; - } - } - // There is only one bridge that can be built - if (type2 == 0 && type != 0) type2 = type; - - // Now, simply, build the bridge! - if (p->ainew.tbt == AI_TRAIN) { - return AI_DoCommand(tile_a, tile_b, (0x00 << 8) + type2, flag | DC_AUTO, CMD_BUILD_BRIDGE); - } else { - return AI_DoCommand(tile_a, tile_b, (0x80 << 8) + type2, flag | DC_AUTO, CMD_BUILD_BRIDGE); - } -} - - -// Build the route part by part -// Basicly what this function do, is build that amount of parts of the route -// that go in the same direction. It sets 'part' to the last part of the route builded. -// The return value is the cost for the builded parts -// -// Params: -// PathFinderInfo : Pointer to the PathFinderInfo used for AiPathFinder -// part : Which part we need to build -// -// TODO: skip already builded road-pieces (e.g.: cityroad) -int AiNew_Build_RoutePart(Player *p, Ai_PathFinderInfo *PathFinderInfo, byte flag) -{ - int part = PathFinderInfo->position; - byte *route_extra = PathFinderInfo->route_extra; - TileIndex *route = PathFinderInfo->route; - int dir; - int old_dir = -1; - int cost = 0; - int res; - // We need to calculate the direction with the parent of the parent.. so we skip - // the first pieces and the last piece - if (part < 1) part = 1; - // When we are done, stop it - if (part >= PathFinderInfo->route_length - 1) { - PathFinderInfo->position = -2; - return 0; - } - - - if (PathFinderInfo->rail_or_road) { - // Tunnel code - if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { - cost += AI_DoCommand(route[part], 0, 0, flag, CMD_BUILD_TUNNEL); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); - return 0; - } - return cost; - } - // Bridge code - if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { - cost += AiNew_Build_Bridge(p, route[part], route[part-1], flag); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part - 1]); - return 0; - } - return cost; - } - - // Build normal rail - // Keep it doing till we go an other way - if (route_extra[part - 1] == 0 && route_extra[part] == 0) { - while (route_extra[part] == 0) { - // Get the current direction - dir = AiNew_GetRailDirection(route[part-1], route[part], route[part+1]); - // Is it the same as the last one? - if (old_dir != -1 && old_dir != dir) break; - old_dir = dir; - // Build the tile - res = AI_DoCommand(route[part], 0, dir, flag, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(res)) { - // Problem.. let's just abort it all! - p->ainew.state = AI_STATE_NOTHING; - return 0; - } - cost += res; - // Go to the next tile - part++; - // Check if it is still in range.. - if (part >= PathFinderInfo->route_length - 1) break; - } - part--; - } - // We want to return the last position, so we go back one - PathFinderInfo->position = part; - } else { - // Tunnel code - if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { - cost += AI_DoCommand(route[part], 0x200, 0, flag, CMD_BUILD_TUNNEL); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); - return 0; - } - return cost; - } - // Bridge code - if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { - cost += AiNew_Build_Bridge(p, route[part], route[part+1], flag); - PathFinderInfo->position++; - // TODO: problems! - if (CmdFailed(cost)) { - DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part + 1]); - return 0; - } - return cost; - } - - // Build normal road - // Keep it doing till we go an other way - // EnsureNoVehicle makes sure we don't build on a tile where a vehicle is. This way - // it will wait till the vehicle is gone.. - if (route_extra[part-1] == 0 && route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicle(route[part]))) { - while (route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicle(route[part]))) { - // Get the current direction - dir = AiNew_GetRoadDirection(route[part-1], route[part], route[part+1]); - // Is it the same as the last one? - if (old_dir != -1 && old_dir != dir) break; - old_dir = dir; - // There is already some road, and it is a bridge.. don't build!!! - if (!IsTileType(route[part], MP_TUNNELBRIDGE)) { - // Build the tile - res = AI_DoCommand(route[part], dir, 0, flag | DC_NO_WATER, CMD_BUILD_ROAD); - // Currently, we ignore CMD_ERRORs! - if (CmdFailed(res) && flag == DC_EXEC && !IsTileType(route[part], MP_STREET) && !EnsureNoVehicle(route[part])) { - // Problem.. let's just abort it all! - DEBUG(ai, 0, "[BuidPath] route building failed at tile 0x%X, aborting", route[part]); - p->ainew.state = AI_STATE_NOTHING; - return 0; - } - - if (!CmdFailed(res)) cost += res; - } - // Go to the next tile - part++; - // Check if it is still in range.. - if (part >= PathFinderInfo->route_length - 1) break; - } - part--; - // We want to return the last position, so we go back one - } - if (!EnsureNoVehicle(route[part]) && flag == DC_EXEC) part--; - PathFinderInfo->position = part; - } - - return cost; -} - - -// This functions tries to find the best vehicle for this type of cargo -// It returns INVALID_ENGINE if not suitable engine is found -EngineID AiNew_PickVehicle(Player *p) -{ - if (p->ainew.tbt == AI_TRAIN) { - // Not supported yet - return INVALID_ENGINE; - } else { - EngineID best_veh_index = INVALID_ENGINE; - int32 best_veh_rating = 0; - EngineID start = ROAD_ENGINES_INDEX; - EngineID end = ROAD_ENGINES_INDEX + NUM_ROAD_ENGINES; - EngineID i; - - /* Loop through all road vehicles */ - for (i = start; i != end; i++) { - const RoadVehicleInfo *rvi = RoadVehInfo(i); - const Engine* e = GetEngine(i); - int32 rating; - int32 ret; - - /* Skip vehicles which can't take our cargo type */ - if (rvi->cargo_type != p->ainew.cargo && !CanRefitTo(i, p->ainew.cargo)) continue; - - // Is it availiable? - // Also, check if the reliability of the vehicle is above the AI_VEHICLE_MIN_RELIABILTY - if (!HASBIT(e->player_avail, _current_player) || e->reliability * 100 < AI_VEHICLE_MIN_RELIABILTY << 16) continue; - - /* Rate and compare the engine by speed & capacity */ - rating = rvi->max_speed * rvi->capacity; - if (rating <= best_veh_rating) continue; - - // Can we build it? - ret = AI_DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_ROAD_VEH); - if (CmdFailed(ret)) continue; - - best_veh_rating = rating; - best_veh_index = i; - } - - return best_veh_index; - } -} - - -void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - Player* p = GetPlayer(_current_player); - - if (success) { - p->ainew.state = AI_STATE_GIVE_ORDERS; - p->ainew.veh_id = _new_vehicle_id; - - if (GetVehicle(p->ainew.veh_id)->cargo_type != p->ainew.cargo) { - /* Cargo type doesn't match, so refit it */ - if (CmdFailed(DoCommand(tile, p->ainew.veh_id, p->ainew.cargo, DC_EXEC, CMD_REFIT_ROAD_VEH))) { - /* Refit failed, so sell the vehicle */ - DoCommand(tile, p->ainew.veh_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - p->ainew.state = AI_STATE_NOTHING; - } - } - } else { - /* XXX this should be handled more gracefully */ - p->ainew.state = AI_STATE_NOTHING; - } -} - - -// Builds the best vehicle possible -int AiNew_Build_Vehicle(Player *p, TileIndex tile, byte flag) -{ - EngineID i = AiNew_PickVehicle(p); - - if (i == INVALID_ENGINE) return CMD_ERROR; - if (p->ainew.tbt == AI_TRAIN) return CMD_ERROR; - - if (flag & DC_EXEC) { - return AI_DoCommandCc(tile, i, 0, flag, CMD_BUILD_ROAD_VEH, CcAI); - } else { - return AI_DoCommand(tile, i, 0, flag, CMD_BUILD_ROAD_VEH); - } -} - -int AiNew_Build_Depot(Player* p, TileIndex tile, DiagDirection direction, byte flag) -{ - int ret, ret2; - if (p->ainew.tbt == AI_TRAIN) { - return AI_DoCommand(tile, 0, direction, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_TRAIN_DEPOT); - } else { - ret = AI_DoCommand(tile, direction, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_DEPOT); - if (CmdFailed(ret)) return ret; - // Try to build the road from the depot - ret2 = AI_DoCommand(tile + TileOffsByDiagDir(direction), DiagDirToRoadBits(ReverseDiagDir(direction)), 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - // If it fails, ignore it.. - if (CmdFailed(ret2)) return ret; - return ret + ret2; - } -} diff --git a/src/ai/trolly/build.cpp b/src/ai/trolly/build.cpp new file mode 100644 --- /dev/null +++ b/src/ai/trolly/build.cpp @@ -0,0 +1,324 @@ +/* $Id$ */ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../debug.h" +#include "../../functions.h" +#include "../../map.h" +#include "../../road_map.h" +#include "../../tile.h" +#include "../../vehicle.h" +#include "../../command.h" +#include "trolly.h" +#include "../../engine.h" +#include "../../station.h" +#include "../../variables.h" +#include "../../bridge.h" +#include "../ai.h" + +// Build HQ +// Params: +// tile : tile where HQ is going to be build +bool AiNew_Build_CompanyHQ(Player *p, TileIndex tile) +{ + if (CmdFailed(AI_DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ))) + return false; + AI_DoCommand(tile, 0, 0, DC_EXEC | DC_AUTO | DC_NO_WATER, CMD_BUILD_COMPANY_HQ); + return true; +} + + +// Build station +// Params: +// type : AI_TRAIN/AI_BUS/AI_TRUCK : indicates the type of station +// tile : tile where station is going to be build +// length : in case of AI_TRAIN: length of station +// numtracks : in case of AI_TRAIN: tracks of station +// direction : the direction of the station +// flag : flag passed to DoCommand (normally 0 to get the cost or DC_EXEC to build it) +int AiNew_Build_Station(Player *p, byte type, TileIndex tile, byte length, byte numtracks, byte direction, byte flag) +{ + if (type == AI_TRAIN) + return AI_DoCommand(tile, direction + (numtracks << 8) + (length << 16), 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_RAILROAD_STATION); + + if (type == AI_BUS) + return AI_DoCommand(tile, direction, RS_BUS, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); + + return AI_DoCommand(tile, direction, RS_TRUCK, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_STOP); +} + + +// Builds a brdige. The second best out of the ones available for this player +// Params: +// tile_a : starting point +// tile_b : end point +// flag : flag passed to DoCommand +int AiNew_Build_Bridge(Player *p, TileIndex tile_a, TileIndex tile_b, byte flag) +{ + int bridge_type, bridge_len, type, type2; + + // Find a good bridgetype (the best money can buy) + bridge_len = GetBridgeLength(tile_a, tile_b); + type = type2 = 0; + for (bridge_type = MAX_BRIDGES-1; bridge_type >= 0; bridge_type--) { + if (CheckBridge_Stuff(bridge_type, bridge_len)) { + type2 = type; + type = bridge_type; + // We found two bridges, exit + if (type2 != 0) break; + } + } + // There is only one bridge that can be built + if (type2 == 0 && type != 0) type2 = type; + + // Now, simply, build the bridge! + if (p->ainew.tbt == AI_TRAIN) { + return AI_DoCommand(tile_a, tile_b, (0x00 << 8) + type2, flag | DC_AUTO, CMD_BUILD_BRIDGE); + } else { + return AI_DoCommand(tile_a, tile_b, (0x80 << 8) + type2, flag | DC_AUTO, CMD_BUILD_BRIDGE); + } +} + + +// Build the route part by part +// Basicly what this function do, is build that amount of parts of the route +// that go in the same direction. It sets 'part' to the last part of the route builded. +// The return value is the cost for the builded parts +// +// Params: +// PathFinderInfo : Pointer to the PathFinderInfo used for AiPathFinder +// part : Which part we need to build +// +// TODO: skip already builded road-pieces (e.g.: cityroad) +int AiNew_Build_RoutePart(Player *p, Ai_PathFinderInfo *PathFinderInfo, byte flag) +{ + int part = PathFinderInfo->position; + byte *route_extra = PathFinderInfo->route_extra; + TileIndex *route = PathFinderInfo->route; + int dir; + int old_dir = -1; + int cost = 0; + int res; + // We need to calculate the direction with the parent of the parent.. so we skip + // the first pieces and the last piece + if (part < 1) part = 1; + // When we are done, stop it + if (part >= PathFinderInfo->route_length - 1) { + PathFinderInfo->position = -2; + return 0; + } + + + if (PathFinderInfo->rail_or_road) { + // Tunnel code + if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { + cost += AI_DoCommand(route[part], 0, 0, flag, CMD_BUILD_TUNNEL); + PathFinderInfo->position++; + // TODO: problems! + if (CmdFailed(cost)) { + DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); + return 0; + } + return cost; + } + // Bridge code + if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { + cost += AiNew_Build_Bridge(p, route[part], route[part-1], flag); + PathFinderInfo->position++; + // TODO: problems! + if (CmdFailed(cost)) { + DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part - 1]); + return 0; + } + return cost; + } + + // Build normal rail + // Keep it doing till we go an other way + if (route_extra[part - 1] == 0 && route_extra[part] == 0) { + while (route_extra[part] == 0) { + // Get the current direction + dir = AiNew_GetRailDirection(route[part-1], route[part], route[part+1]); + // Is it the same as the last one? + if (old_dir != -1 && old_dir != dir) break; + old_dir = dir; + // Build the tile + res = AI_DoCommand(route[part], 0, dir, flag, CMD_BUILD_SINGLE_RAIL); + if (CmdFailed(res)) { + // Problem.. let's just abort it all! + p->ainew.state = AI_STATE_NOTHING; + return 0; + } + cost += res; + // Go to the next tile + part++; + // Check if it is still in range.. + if (part >= PathFinderInfo->route_length - 1) break; + } + part--; + } + // We want to return the last position, so we go back one + PathFinderInfo->position = part; + } else { + // Tunnel code + if ((AI_PATHFINDER_FLAG_TUNNEL & route_extra[part]) != 0) { + cost += AI_DoCommand(route[part], 0x200, 0, flag, CMD_BUILD_TUNNEL); + PathFinderInfo->position++; + // TODO: problems! + if (CmdFailed(cost)) { + DEBUG(ai, 0, "[BuildPath] tunnel could not be built (0x%X)", route[part]); + return 0; + } + return cost; + } + // Bridge code + if ((AI_PATHFINDER_FLAG_BRIDGE & route_extra[part]) != 0) { + cost += AiNew_Build_Bridge(p, route[part], route[part+1], flag); + PathFinderInfo->position++; + // TODO: problems! + if (CmdFailed(cost)) { + DEBUG(ai, 0, "[BuildPath] bridge could not be built (0x%X, 0x%X)", route[part], route[part + 1]); + return 0; + } + return cost; + } + + // Build normal road + // Keep it doing till we go an other way + // EnsureNoVehicle makes sure we don't build on a tile where a vehicle is. This way + // it will wait till the vehicle is gone.. + if (route_extra[part-1] == 0 && route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicle(route[part]))) { + while (route_extra[part] == 0 && (flag != DC_EXEC || EnsureNoVehicle(route[part]))) { + // Get the current direction + dir = AiNew_GetRoadDirection(route[part-1], route[part], route[part+1]); + // Is it the same as the last one? + if (old_dir != -1 && old_dir != dir) break; + old_dir = dir; + // There is already some road, and it is a bridge.. don't build!!! + if (!IsTileType(route[part], MP_TUNNELBRIDGE)) { + // Build the tile + res = AI_DoCommand(route[part], dir, 0, flag | DC_NO_WATER, CMD_BUILD_ROAD); + // Currently, we ignore CMD_ERRORs! + if (CmdFailed(res) && flag == DC_EXEC && !IsTileType(route[part], MP_STREET) && !EnsureNoVehicle(route[part])) { + // Problem.. let's just abort it all! + DEBUG(ai, 0, "[BuidPath] route building failed at tile 0x%X, aborting", route[part]); + p->ainew.state = AI_STATE_NOTHING; + return 0; + } + + if (!CmdFailed(res)) cost += res; + } + // Go to the next tile + part++; + // Check if it is still in range.. + if (part >= PathFinderInfo->route_length - 1) break; + } + part--; + // We want to return the last position, so we go back one + } + if (!EnsureNoVehicle(route[part]) && flag == DC_EXEC) part--; + PathFinderInfo->position = part; + } + + return cost; +} + + +// This functions tries to find the best vehicle for this type of cargo +// It returns INVALID_ENGINE if not suitable engine is found +EngineID AiNew_PickVehicle(Player *p) +{ + if (p->ainew.tbt == AI_TRAIN) { + // Not supported yet + return INVALID_ENGINE; + } else { + EngineID best_veh_index = INVALID_ENGINE; + int32 best_veh_rating = 0; + EngineID start = ROAD_ENGINES_INDEX; + EngineID end = ROAD_ENGINES_INDEX + NUM_ROAD_ENGINES; + EngineID i; + + /* Loop through all road vehicles */ + for (i = start; i != end; i++) { + const RoadVehicleInfo *rvi = RoadVehInfo(i); + const Engine* e = GetEngine(i); + int32 rating; + int32 ret; + + /* Skip vehicles which can't take our cargo type */ + if (rvi->cargo_type != p->ainew.cargo && !CanRefitTo(i, p->ainew.cargo)) continue; + + // Is it availiable? + // Also, check if the reliability of the vehicle is above the AI_VEHICLE_MIN_RELIABILTY + if (!HASBIT(e->player_avail, _current_player) || e->reliability * 100 < AI_VEHICLE_MIN_RELIABILTY << 16) continue; + + /* Rate and compare the engine by speed & capacity */ + rating = rvi->max_speed * rvi->capacity; + if (rating <= best_veh_rating) continue; + + // Can we build it? + ret = AI_DoCommand(0, i, 0, DC_QUERY_COST, CMD_BUILD_ROAD_VEH); + if (CmdFailed(ret)) continue; + + best_veh_rating = rating; + best_veh_index = i; + } + + return best_veh_index; + } +} + + +void CcAI(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + Player* p = GetPlayer(_current_player); + + if (success) { + p->ainew.state = AI_STATE_GIVE_ORDERS; + p->ainew.veh_id = _new_vehicle_id; + + if (GetVehicle(p->ainew.veh_id)->cargo_type != p->ainew.cargo) { + /* Cargo type doesn't match, so refit it */ + if (CmdFailed(DoCommand(tile, p->ainew.veh_id, p->ainew.cargo, DC_EXEC, CMD_REFIT_ROAD_VEH))) { + /* Refit failed, so sell the vehicle */ + DoCommand(tile, p->ainew.veh_id, 0, DC_EXEC, CMD_SELL_ROAD_VEH); + p->ainew.state = AI_STATE_NOTHING; + } + } + } else { + /* XXX this should be handled more gracefully */ + p->ainew.state = AI_STATE_NOTHING; + } +} + + +// Builds the best vehicle possible +int AiNew_Build_Vehicle(Player *p, TileIndex tile, byte flag) +{ + EngineID i = AiNew_PickVehicle(p); + + if (i == INVALID_ENGINE) return CMD_ERROR; + if (p->ainew.tbt == AI_TRAIN) return CMD_ERROR; + + if (flag & DC_EXEC) { + return AI_DoCommandCc(tile, i, 0, flag, CMD_BUILD_ROAD_VEH, CcAI); + } else { + return AI_DoCommand(tile, i, 0, flag, CMD_BUILD_ROAD_VEH); + } +} + +int AiNew_Build_Depot(Player* p, TileIndex tile, DiagDirection direction, byte flag) +{ + int ret, ret2; + if (p->ainew.tbt == AI_TRAIN) { + return AI_DoCommand(tile, 0, direction, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_TRAIN_DEPOT); + } else { + ret = AI_DoCommand(tile, direction, 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD_DEPOT); + if (CmdFailed(ret)) return ret; + // Try to build the road from the depot + ret2 = AI_DoCommand(tile + TileOffsByDiagDir(direction), DiagDirToRoadBits(ReverseDiagDir(direction)), 0, flag | DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); + // If it fails, ignore it.. + if (CmdFailed(ret2)) return ret; + return ret + ret2; + } +} diff --git a/src/ai/trolly/pathfinder.c b/src/ai/trolly/pathfinder.c deleted file mode 100644 --- a/src/ai/trolly/pathfinder.c +++ /dev/null @@ -1,510 +0,0 @@ -/* $Id$ */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../bridge_map.h" -#include "../../debug.h" -#include "../../functions.h" -#include "../../map.h" -#include "../../tile.h" -#include "../../command.h" -#include "trolly.h" -#include "../../depot.h" -#include "../../tunnel_map.h" -#include "../../bridge.h" -#include "../ai.h" - -#define TEST_STATION_NO_DIR 0xFF - -// Tests if a station can be build on the given spot -// TODO: make it train compatible -static bool TestCanBuildStationHere(TileIndex tile, byte dir) -{ - Player *p = GetPlayer(_current_player); - - if (dir == TEST_STATION_NO_DIR) { - int32 ret; - // TODO: currently we only allow spots that can be access from al 4 directions... - // should be fixed!!! - for (dir = 0; dir < 4; dir++) { - ret = AiNew_Build_Station(p, p->ainew.tbt, tile, 1, 1, dir, DC_QUERY_COST); - if (!CmdFailed(ret)) return true; - } - return false; - } - - // return true if command succeeded, so the inverse of CmdFailed() - return !CmdFailed(AiNew_Build_Station(p, p->ainew.tbt, tile, 1, 1, dir, DC_QUERY_COST)); -} - - -static bool IsRoad(TileIndex tile) -{ - return - // MP_STREET, but not a road depot? - (IsTileType(tile, MP_STREET) && !IsTileDepotType(tile, TRANSPORT_ROAD)) || - (IsTileType(tile, MP_TUNNELBRIDGE) && ( - (IsTunnel(tile) && GetTunnelTransportType(tile) == TRANSPORT_ROAD) || - (IsBridge(tile) && GetBridgeTransportType(tile) == TRANSPORT_ROAD) - )); -} - - -// Checks if a tile 'a' is between the tiles 'b' and 'c' -#define TILES_BETWEEN(a, b, c) (TileX(a) >= TileX(b) && TileX(a) <= TileX(c) && TileY(a) >= TileY(b) && TileY(a) <= TileY(c)) - - -// Check if the current tile is in our end-area -static int32 AyStar_AiPathFinder_EndNodeCheck(AyStar *aystar, OpenListNode *current) -{ - const Ai_PathFinderInfo* PathFinderInfo = aystar->user_target; - - // It is not allowed to have a station on the end of a bridge or tunnel ;) - if (current->path.node.user_data[0] != 0) return AYSTAR_DONE; - if (TILES_BETWEEN(current->path.node.tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) - if (IsTileType(current->path.node.tile, MP_CLEAR) || IsTileType(current->path.node.tile, MP_TREES)) - if (current->path.parent == NULL || TestCanBuildStationHere(current->path.node.tile, AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile))) - return AYSTAR_FOUND_END_NODE; - - return AYSTAR_DONE; -} - - -// Calculates the hash -// Currently it is a 10 bit hash, so the hash array has a max depth of 6 bits (so 64) -static uint AiPathFinder_Hash(uint key1, uint key2) -{ - return (TileX(key1) & 0x1F) + ((TileY(key1) & 0x1F) << 5); -} - - -// Clear the memory of all the things -static void AyStar_AiPathFinder_Free(AyStar *aystar) -{ - AyStarMain_Free(aystar); - free(aystar); -} - - -static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent); -static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent); -static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current); -static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current); - - -// This creates the AiPathFinder -AyStar *new_AyStar_AiPathFinder(int max_tiles_around, Ai_PathFinderInfo *PathFinderInfo) -{ - PathNode start_node; - uint x; - uint y; - // Create AyStar - AyStar *result = malloc(sizeof(AyStar)); - init_AyStar(result, AiPathFinder_Hash, 1 << 10); - // Set the function pointers - result->CalculateG = AyStar_AiPathFinder_CalculateG; - result->CalculateH = AyStar_AiPathFinder_CalculateH; - result->EndNodeCheck = AyStar_AiPathFinder_EndNodeCheck; - result->FoundEndNode = AyStar_AiPathFinder_FoundEndNode; - result->GetNeighbours = AyStar_AiPathFinder_GetNeighbours; - - result->free = AyStar_AiPathFinder_Free; - - // Set some information - result->loops_per_tick = AI_PATHFINDER_LOOPS_PER_TICK; - result->max_path_cost = 0; - result->max_search_nodes = AI_PATHFINDER_MAX_SEARCH_NODES; - - // Set the user_data to the PathFinderInfo - result->user_target = PathFinderInfo; - - // Set the start node - start_node.parent = NULL; - start_node.node.direction = 0; - start_node.node.user_data[0] = 0; - - // Now we add all the starting tiles - for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { - for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { - start_node.node.tile = TileXY(x, y); - result->addstart(result, &start_node.node, 0); - } - } - - return result; -} - - -// To reuse AyStar we sometimes have to clean all the memory -void clean_AyStar_AiPathFinder(AyStar *aystar, Ai_PathFinderInfo *PathFinderInfo) -{ - PathNode start_node; - uint x; - uint y; - - aystar->clear(aystar); - - // Set the user_data to the PathFinderInfo - aystar->user_target = PathFinderInfo; - - // Set the start node - start_node.parent = NULL; - start_node.node.direction = 0; - start_node.node.user_data[0] = 0; - start_node.node.tile = PathFinderInfo->start_tile_tl; - - // Now we add all the starting tiles - for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { - for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { - TileIndex tile = TileXY(x, y); - - if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES)) continue; - if (!TestCanBuildStationHere(tile, TEST_STATION_NO_DIR)) continue; - start_node.node.tile = tile; - aystar->addstart(aystar, &start_node.node, 0); - } - } -} - - -// The h-value, simple calculation -static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - const Ai_PathFinderInfo* PathFinderInfo = aystar->user_target; - int r, r2; - - if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION) { - // The station is pointing to a direction, add a tile towards that direction, so the H-value is more accurate - r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl + TileOffsByDiagDir(PathFinderInfo->end_direction)); - r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br + TileOffsByDiagDir(PathFinderInfo->end_direction)); - } else { - // No direction, so just get the fastest route to the station - r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl); - r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br); - } - // See if the bottomright is faster than the topleft.. - if (r2 < r) r = r2; - return r * AI_PATHFINDER_H_MULTIPLER; -} - - -// We found the end.. let's get the route back and put it in an array -static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current) -{ - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - uint i = 0; - PathNode *parent = ¤t->path; - - do { - PathFinderInfo->route_extra[i] = parent->node.user_data[0]; - PathFinderInfo->route[i++] = parent->node.tile; - if (i > lengthof(PathFinderInfo->route)) { - // We ran out of space for the PathFinder - DEBUG(ai, 0, "No more space in pathfinder route[] array"); - PathFinderInfo->route_length = -1; // -1 indicates out of space - return; - } - parent = parent->parent; - } while (parent != NULL); - PathFinderInfo->route_length = i; - DEBUG(ai, 1, "Found route of %d nodes long in %d nodes of searching", i, Hash_Size(&aystar->ClosedListHash)); -} - - -// What tiles are around us. -static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current) -{ - uint i; - int ret; - int dir; - - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - - aystar->num_neighbours = 0; - - // Go through all surrounding tiles and check if they are within the limits - for (i = 0; i < 4; i++) { - TileIndex ctile = current->path.node.tile; // Current tile - TileIndex atile = ctile + TileOffsByDiagDir(i); // Adjacent tile - - if (TileX(atile) > 1 && TileX(atile) < MapMaxX() - 1 && - TileY(atile) > 1 && TileY(atile) < MapMaxY() - 1) { - // We also directly test if the current tile can connect to this tile.. - // We do this simply by just building the tile! - - // If the next step is a bridge, we have to enter it the right way - if (!PathFinderInfo->rail_or_road && IsRoad(atile)) { - if (IsTileType(atile, MP_TUNNELBRIDGE)) { - if (IsTunnel(atile)) { - if (GetTunnelDirection(atile) != i) continue; - } else { - if ((_m[atile].m5 & 1U) != DiagDirToAxis(i)) continue; - } - } - } - // But also if we are on a bridge, we can only move a certain direction - if (!PathFinderInfo->rail_or_road && IsRoad(ctile)) { - if (IsTileType(ctile, MP_TUNNELBRIDGE)) { - // An existing bridge/tunnel... let's test the direction ;) - if ((_m[ctile].m5 & 1U) != (i & 1)) continue; - } - } - - if ((AI_PATHFINDER_FLAG_BRIDGE & current->path.node.user_data[0]) != 0 || - (AI_PATHFINDER_FLAG_TUNNEL & current->path.node.user_data[0]) != 0) { - // We are a bridge/tunnel, how cool!! - // This means we can only point forward.. get the direction from the user_data - if (i != (current->path.node.user_data[0] >> 8)) continue; - } - dir = 0; - - // First, check if we have a parent - if (current->path.parent == NULL && current->path.node.user_data[0] == 0) { - // If not, this means we are at the starting station - if (PathFinderInfo->start_direction != AI_PATHFINDER_NO_DIRECTION) { - // We do need a direction? - if (AiNew_GetDirection(ctile, atile) != PathFinderInfo->start_direction) { - // We are not pointing the right way, invalid tile - continue; - } - } - } else if (current->path.node.user_data[0] == 0) { - if (PathFinderInfo->rail_or_road) { - // Rail check - dir = AiNew_GetRailDirection(current->path.parent->node.tile, ctile, atile); - ret = AI_DoCommand(ctile, 0, dir, DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); - if (CmdFailed(ret)) continue; -#ifdef AI_PATHFINDER_NO_90DEGREES_TURN - if (current->path.parent->parent != NULL) { - // Check if we don't make a 90degree curve - int dir1 = AiNew_GetRailDirection(current->path.parent->parent->node.tile, current->path.parent->node.tile, ctile); - if (_illegal_curves[dir1] == dir || _illegal_curves[dir] == dir1) { - continue; - } - } -#endif - } else { - // Road check - dir = AiNew_GetRoadDirection(current->path.parent->node.tile, ctile, atile); - if (IsRoad(ctile)) { - if (IsTileType(ctile, MP_TUNNELBRIDGE)) { - // We have a bridge, how nicely! We should mark it... - dir = 0; - } else { - // It already has road.. check if we miss any bits! - if ((_m[ctile].m5 & dir) != dir) { - // We do miss some pieces :( - dir &= ~_m[ctile].m5; - } else { - dir = 0; - } - } - } - // Only destruct things if it is MP_CLEAR of MP_TREES - if (dir != 0) { - ret = AI_DoCommand(ctile, dir, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); - if (CmdFailed(ret)) continue; - } - } - } - - // The tile can be connected - aystar->neighbours[aystar->num_neighbours].tile = atile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = 0; - aystar->neighbours[aystar->num_neighbours++].direction = 0; - } - } - - // Next step, check for bridges and tunnels - if (current->path.parent != NULL && current->path.node.user_data[0] == 0) { - // First we get the dir from this tile and his parent - DiagDirection dir = AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile); - // It means we can only walk with the track, so the bridge has to be in the same direction - TileIndex tile = current->path.node.tile; - TileIndex new_tile = tile; - Slope tileh = GetTileSlope(tile, NULL); - - // Bridges can only be build on land that is not flat - // And if there is a road or rail blocking - if (tileh != SLOPE_FLAT || - (PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_STREET)) || - (!PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_RAILWAY))) { - for (;;) { - new_tile += TileOffsByDiagDir(dir); - - // Precheck, is the length allowed? - if (!CheckBridge_Stuff(0, GetBridgeLength(tile, new_tile))) break; - - // Check if we hit the station-tile.. we don't like that! - if (TILES_BETWEEN(new_tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) break; - - // Try building the bridge.. - ret = AI_DoCommand(tile, new_tile, (0 << 8) + (MAX_BRIDGES / 2), DC_AUTO, CMD_BUILD_BRIDGE); - if (CmdFailed(ret)) continue; - // We can build a bridge here.. add him to the neighbours - aystar->neighbours[aystar->num_neighbours].tile = new_tile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_BRIDGE + (dir << 8); - aystar->neighbours[aystar->num_neighbours++].direction = 0; - // We can only have 12 neighbours, and we need 1 left for tunnels - if (aystar->num_neighbours == 11) break; - } - } - - // Next, check for tunnels! - // Tunnels can only be built on slopes corresponding to the direction - // For now, we check both sides for this tile.. terraforming gives fuzzy result - if ((dir == DIAGDIR_NE && tileh == SLOPE_NE) || - (dir == DIAGDIR_SE && tileh == SLOPE_SE) || - (dir == DIAGDIR_SW && tileh == SLOPE_SW) || - (dir == DIAGDIR_NW && tileh == SLOPE_NW)) { - // Now simply check if a tunnel can be build - ret = AI_DoCommand(tile, (PathFinderInfo->rail_or_road?0:0x200), 0, DC_AUTO, CMD_BUILD_TUNNEL); - tileh = GetTileSlope(_build_tunnel_endtile, NULL); - if (!CmdFailed(ret) && (tileh == SLOPE_SW || tileh == SLOPE_SE || tileh == SLOPE_NW || tileh == SLOPE_NE)) { - aystar->neighbours[aystar->num_neighbours].tile = _build_tunnel_endtile; - aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_TUNNEL + (dir << 8); - aystar->neighbours[aystar->num_neighbours++].direction = 0; - } - } - } -} - - -extern uint GetRailFoundation(Slope tileh, TrackBits bits); // XXX function declaration in .c -extern uint GetRoadFoundation(Slope tileh, uint bits); // XXX function declaration in .c -extern uint GetBridgeFoundation(Slope tileh, Axis); // XXX function declaration in .c -enum { - BRIDGE_NO_FOUNDATION = 1 << 0 | 1 << 3 | 1 << 6 | 1 << 9 | 1 << 12, -}; - -// The most important function: it calculates the g-value -static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; - int r, res = 0; - Slope tileh = GetTileSlope(current->tile, NULL); - Slope parent_tileh = GetTileSlope(parent->path.node.tile, NULL); - - // Check if we hit the end-tile - if (TILES_BETWEEN(current->tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) { - // We are at the end-tile, check if we had a direction or something... - if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION && AiNew_GetDirection(current->tile, parent->path.node.tile) != PathFinderInfo->end_direction) { - // We are not pointing the right way, invalid tile - return AYSTAR_INVALID_NODE; - } - // If it was valid, drop out.. we don't build on the endtile - return 0; - } - - // Give everything a small penalty - res += AI_PATHFINDER_PENALTY; - - if (!PathFinderInfo->rail_or_road) { - // Road has the lovely advantage it can use other road... check if - // the current tile is road, and if so, give a good bonus - if (IsRoad(current->tile)) { - res -= AI_PATHFINDER_ROAD_ALREADY_EXISTS_BONUS; - } - } - - // We should give a penalty when the tile is going up or down.. this is one way to do so! - // Too bad we have to count it from the parent.. but that is not so bad. - // We also dislike long routes on slopes, since they do not look too realistic - // when there is a flat land all around, they are more expensive to build, and - // especially they essentially block the ability to connect or cross the road - // from one side. - if (parent_tileh != SLOPE_FLAT && parent->path.parent != NULL) { - // Skip if the tile was from a bridge or tunnel - if (parent->path.node.user_data[0] == 0 && current->user_data[0] == 0) { - if (PathFinderInfo->rail_or_road) { - r = GetRailFoundation(parent_tileh, 1 << AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile)); - // Maybe is BRIDGE_NO_FOUNDATION a bit strange here, but it contains just the right information.. - if (r >= 15 || (r == 0 && HASBIT(BRIDGE_NO_FOUNDATION, tileh))) { - res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; - } else { - res += AI_PATHFINDER_FOUNDATION_PENALTY; - } - } else { - if (!IsRoad(parent->path.node.tile) || !IsTileType(parent->path.node.tile, MP_TUNNELBRIDGE)) { - r = GetRoadFoundation(parent_tileh, AiNew_GetRoadDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile)); - if (r >= 15 || r == 0) { - res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; - } else { - res += AI_PATHFINDER_FOUNDATION_PENALTY; - } - } - } - } - } - - // Are we part of a tunnel? - if ((AI_PATHFINDER_FLAG_TUNNEL & current->user_data[0]) != 0) { - // Tunnels are very expensive when build on long routes.. - // Ironicly, we are using BridgeCode here ;) - r = AI_PATHFINDER_TUNNEL_PENALTY * GetBridgeLength(current->tile, parent->path.node.tile); - res += r + (r >> 8); - } - - // Are we part of a bridge? - if ((AI_PATHFINDER_FLAG_BRIDGE & current->user_data[0]) != 0) { - // That means for every length a penalty - res += AI_PATHFINDER_BRIDGE_PENALTY * GetBridgeLength(current->tile, parent->path.node.tile); - // Check if we are going up or down, first for the starting point - // In user_data[0] is at the 8th bit the direction - if (!HASBIT(BRIDGE_NO_FOUNDATION, parent_tileh)) { - if (GetBridgeFoundation(parent_tileh, (current->user_data[0] >> 8) & 1) < 15) { - res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - } - } - // Second for the end point - if (!HASBIT(BRIDGE_NO_FOUNDATION, tileh)) { - if (GetBridgeFoundation(tileh, (current->user_data[0] >> 8) & 1) < 15) { - res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - } - } - if (parent_tileh == SLOPE_FLAT) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - if (tileh == SLOPE_FLAT) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; - } - - // To prevent the AI from taking the fastest way in tiles, but not the fastest way - // in speed, we have to give a good penalty to direction changing - // This way, we get almost the fastest way in tiles, and a very good speed on the track - if (!PathFinderInfo->rail_or_road) { - if (parent->path.parent != NULL && - AiNew_GetDirection(current->tile, parent->path.node.tile) != AiNew_GetDirection(parent->path.node.tile, parent->path.parent->node.tile)) { - // When road exists, we don't like turning, but its free, so don't be to piggy about it - if (IsRoad(parent->path.node.tile)) { - res += AI_PATHFINDER_DIRECTION_CHANGE_ON_EXISTING_ROAD_PENALTY; - } else { - res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; - } - } - } else { - // For rail we have 1 exeption: diagonal rail.. - // So we fetch 2 raildirection. That of the current one, and of the one before that - if (parent->path.parent != NULL && parent->path.parent->parent != NULL) { - int dir1 = AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile); - int dir2 = AiNew_GetRailDirection(parent->path.parent->parent->node.tile, parent->path.parent->node.tile, parent->path.node.tile); - // First, see if we are on diagonal path, that is better than straight path - if (dir1 > 1) res -= AI_PATHFINDER_DIAGONAL_BONUS; - - // First see if they are different - if (dir1 != dir2) { - // dir 2 and 3 are 1 diagonal track, and 4 and 5. - if (!(((dir1 == 2 || dir1 == 3) && (dir2 == 2 || dir2 == 3)) || ((dir1 == 4 || dir1 == 5) && (dir2 == 4 || dir2 == 5)))) { - // It is not, so we changed of direction - res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; - } - if (parent->path.parent->parent->parent != NULL) { - int dir3 = AiNew_GetRailDirection(parent->path.parent->parent->parent->node.tile, parent->path.parent->parent->node.tile, parent->path.parent->node.tile); - // Check if we changed 3 tiles of direction in 3 tiles.. bad!!! - if ((dir1 == 0 || dir1 == 1) && dir2 > 1 && (dir3 == 0 || dir3 == 1)) { - res += AI_PATHFINDER_CURVE_PENALTY; - } - } - } - } - } - - return (res < 0) ? 0 : res; -} diff --git a/src/ai/trolly/pathfinder.cpp b/src/ai/trolly/pathfinder.cpp new file mode 100644 --- /dev/null +++ b/src/ai/trolly/pathfinder.cpp @@ -0,0 +1,510 @@ +/* $Id$ */ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../bridge_map.h" +#include "../../debug.h" +#include "../../functions.h" +#include "../../map.h" +#include "../../tile.h" +#include "../../command.h" +#include "trolly.h" +#include "../../depot.h" +#include "../../tunnel_map.h" +#include "../../bridge.h" +#include "../ai.h" + +#define TEST_STATION_NO_DIR 0xFF + +// Tests if a station can be build on the given spot +// TODO: make it train compatible +static bool TestCanBuildStationHere(TileIndex tile, byte dir) +{ + Player *p = GetPlayer(_current_player); + + if (dir == TEST_STATION_NO_DIR) { + int32 ret; + // TODO: currently we only allow spots that can be access from al 4 directions... + // should be fixed!!! + for (dir = 0; dir < 4; dir++) { + ret = AiNew_Build_Station(p, p->ainew.tbt, tile, 1, 1, dir, DC_QUERY_COST); + if (!CmdFailed(ret)) return true; + } + return false; + } + + // return true if command succeeded, so the inverse of CmdFailed() + return !CmdFailed(AiNew_Build_Station(p, p->ainew.tbt, tile, 1, 1, dir, DC_QUERY_COST)); +} + + +static bool IsRoad(TileIndex tile) +{ + return + // MP_STREET, but not a road depot? + (IsTileType(tile, MP_STREET) && !IsTileDepotType(tile, TRANSPORT_ROAD)) || + (IsTileType(tile, MP_TUNNELBRIDGE) && ( + (IsTunnel(tile) && GetTunnelTransportType(tile) == TRANSPORT_ROAD) || + (IsBridge(tile) && GetBridgeTransportType(tile) == TRANSPORT_ROAD) + )); +} + + +// Checks if a tile 'a' is between the tiles 'b' and 'c' +#define TILES_BETWEEN(a, b, c) (TileX(a) >= TileX(b) && TileX(a) <= TileX(c) && TileY(a) >= TileY(b) && TileY(a) <= TileY(c)) + + +// Check if the current tile is in our end-area +static int32 AyStar_AiPathFinder_EndNodeCheck(AyStar *aystar, OpenListNode *current) +{ + const Ai_PathFinderInfo* PathFinderInfo = aystar->user_target; + + // It is not allowed to have a station on the end of a bridge or tunnel ;) + if (current->path.node.user_data[0] != 0) return AYSTAR_DONE; + if (TILES_BETWEEN(current->path.node.tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) + if (IsTileType(current->path.node.tile, MP_CLEAR) || IsTileType(current->path.node.tile, MP_TREES)) + if (current->path.parent == NULL || TestCanBuildStationHere(current->path.node.tile, AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile))) + return AYSTAR_FOUND_END_NODE; + + return AYSTAR_DONE; +} + + +// Calculates the hash +// Currently it is a 10 bit hash, so the hash array has a max depth of 6 bits (so 64) +static uint AiPathFinder_Hash(uint key1, uint key2) +{ + return (TileX(key1) & 0x1F) + ((TileY(key1) & 0x1F) << 5); +} + + +// Clear the memory of all the things +static void AyStar_AiPathFinder_Free(AyStar *aystar) +{ + AyStarMain_Free(aystar); + free(aystar); +} + + +static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent); +static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent); +static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current); +static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current); + + +// This creates the AiPathFinder +AyStar *new_AyStar_AiPathFinder(int max_tiles_around, Ai_PathFinderInfo *PathFinderInfo) +{ + PathNode start_node; + uint x; + uint y; + // Create AyStar + AyStar *result = malloc(sizeof(AyStar)); + init_AyStar(result, AiPathFinder_Hash, 1 << 10); + // Set the function pointers + result->CalculateG = AyStar_AiPathFinder_CalculateG; + result->CalculateH = AyStar_AiPathFinder_CalculateH; + result->EndNodeCheck = AyStar_AiPathFinder_EndNodeCheck; + result->FoundEndNode = AyStar_AiPathFinder_FoundEndNode; + result->GetNeighbours = AyStar_AiPathFinder_GetNeighbours; + + result->free = AyStar_AiPathFinder_Free; + + // Set some information + result->loops_per_tick = AI_PATHFINDER_LOOPS_PER_TICK; + result->max_path_cost = 0; + result->max_search_nodes = AI_PATHFINDER_MAX_SEARCH_NODES; + + // Set the user_data to the PathFinderInfo + result->user_target = PathFinderInfo; + + // Set the start node + start_node.parent = NULL; + start_node.node.direction = 0; + start_node.node.user_data[0] = 0; + + // Now we add all the starting tiles + for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { + for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { + start_node.node.tile = TileXY(x, y); + result->addstart(result, &start_node.node, 0); + } + } + + return result; +} + + +// To reuse AyStar we sometimes have to clean all the memory +void clean_AyStar_AiPathFinder(AyStar *aystar, Ai_PathFinderInfo *PathFinderInfo) +{ + PathNode start_node; + uint x; + uint y; + + aystar->clear(aystar); + + // Set the user_data to the PathFinderInfo + aystar->user_target = PathFinderInfo; + + // Set the start node + start_node.parent = NULL; + start_node.node.direction = 0; + start_node.node.user_data[0] = 0; + start_node.node.tile = PathFinderInfo->start_tile_tl; + + // Now we add all the starting tiles + for (x = TileX(PathFinderInfo->start_tile_tl); x <= TileX(PathFinderInfo->start_tile_br); x++) { + for (y = TileY(PathFinderInfo->start_tile_tl); y <= TileY(PathFinderInfo->start_tile_br); y++) { + TileIndex tile = TileXY(x, y); + + if (!IsTileType(tile, MP_CLEAR) && !IsTileType(tile, MP_TREES)) continue; + if (!TestCanBuildStationHere(tile, TEST_STATION_NO_DIR)) continue; + start_node.node.tile = tile; + aystar->addstart(aystar, &start_node.node, 0); + } + } +} + + +// The h-value, simple calculation +static int32 AyStar_AiPathFinder_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent) +{ + const Ai_PathFinderInfo* PathFinderInfo = aystar->user_target; + int r, r2; + + if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION) { + // The station is pointing to a direction, add a tile towards that direction, so the H-value is more accurate + r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl + TileOffsByDiagDir(PathFinderInfo->end_direction)); + r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br + TileOffsByDiagDir(PathFinderInfo->end_direction)); + } else { + // No direction, so just get the fastest route to the station + r = DistanceManhattan(current->tile, PathFinderInfo->end_tile_tl); + r2 = DistanceManhattan(current->tile, PathFinderInfo->end_tile_br); + } + // See if the bottomright is faster than the topleft.. + if (r2 < r) r = r2; + return r * AI_PATHFINDER_H_MULTIPLER; +} + + +// We found the end.. let's get the route back and put it in an array +static void AyStar_AiPathFinder_FoundEndNode(AyStar *aystar, OpenListNode *current) +{ + Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; + uint i = 0; + PathNode *parent = ¤t->path; + + do { + PathFinderInfo->route_extra[i] = parent->node.user_data[0]; + PathFinderInfo->route[i++] = parent->node.tile; + if (i > lengthof(PathFinderInfo->route)) { + // We ran out of space for the PathFinder + DEBUG(ai, 0, "No more space in pathfinder route[] array"); + PathFinderInfo->route_length = -1; // -1 indicates out of space + return; + } + parent = parent->parent; + } while (parent != NULL); + PathFinderInfo->route_length = i; + DEBUG(ai, 1, "Found route of %d nodes long in %d nodes of searching", i, Hash_Size(&aystar->ClosedListHash)); +} + + +// What tiles are around us. +static void AyStar_AiPathFinder_GetNeighbours(AyStar *aystar, OpenListNode *current) +{ + uint i; + int ret; + int dir; + + Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; + + aystar->num_neighbours = 0; + + // Go through all surrounding tiles and check if they are within the limits + for (i = 0; i < 4; i++) { + TileIndex ctile = current->path.node.tile; // Current tile + TileIndex atile = ctile + TileOffsByDiagDir(i); // Adjacent tile + + if (TileX(atile) > 1 && TileX(atile) < MapMaxX() - 1 && + TileY(atile) > 1 && TileY(atile) < MapMaxY() - 1) { + // We also directly test if the current tile can connect to this tile.. + // We do this simply by just building the tile! + + // If the next step is a bridge, we have to enter it the right way + if (!PathFinderInfo->rail_or_road && IsRoad(atile)) { + if (IsTileType(atile, MP_TUNNELBRIDGE)) { + if (IsTunnel(atile)) { + if (GetTunnelDirection(atile) != i) continue; + } else { + if ((_m[atile].m5 & 1U) != DiagDirToAxis(i)) continue; + } + } + } + // But also if we are on a bridge, we can only move a certain direction + if (!PathFinderInfo->rail_or_road && IsRoad(ctile)) { + if (IsTileType(ctile, MP_TUNNELBRIDGE)) { + // An existing bridge/tunnel... let's test the direction ;) + if ((_m[ctile].m5 & 1U) != (i & 1)) continue; + } + } + + if ((AI_PATHFINDER_FLAG_BRIDGE & current->path.node.user_data[0]) != 0 || + (AI_PATHFINDER_FLAG_TUNNEL & current->path.node.user_data[0]) != 0) { + // We are a bridge/tunnel, how cool!! + // This means we can only point forward.. get the direction from the user_data + if (i != (current->path.node.user_data[0] >> 8)) continue; + } + dir = 0; + + // First, check if we have a parent + if (current->path.parent == NULL && current->path.node.user_data[0] == 0) { + // If not, this means we are at the starting station + if (PathFinderInfo->start_direction != AI_PATHFINDER_NO_DIRECTION) { + // We do need a direction? + if (AiNew_GetDirection(ctile, atile) != PathFinderInfo->start_direction) { + // We are not pointing the right way, invalid tile + continue; + } + } + } else if (current->path.node.user_data[0] == 0) { + if (PathFinderInfo->rail_or_road) { + // Rail check + dir = AiNew_GetRailDirection(current->path.parent->node.tile, ctile, atile); + ret = AI_DoCommand(ctile, 0, dir, DC_AUTO | DC_NO_WATER, CMD_BUILD_SINGLE_RAIL); + if (CmdFailed(ret)) continue; +#ifdef AI_PATHFINDER_NO_90DEGREES_TURN + if (current->path.parent->parent != NULL) { + // Check if we don't make a 90degree curve + int dir1 = AiNew_GetRailDirection(current->path.parent->parent->node.tile, current->path.parent->node.tile, ctile); + if (_illegal_curves[dir1] == dir || _illegal_curves[dir] == dir1) { + continue; + } + } +#endif + } else { + // Road check + dir = AiNew_GetRoadDirection(current->path.parent->node.tile, ctile, atile); + if (IsRoad(ctile)) { + if (IsTileType(ctile, MP_TUNNELBRIDGE)) { + // We have a bridge, how nicely! We should mark it... + dir = 0; + } else { + // It already has road.. check if we miss any bits! + if ((_m[ctile].m5 & dir) != dir) { + // We do miss some pieces :( + dir &= ~_m[ctile].m5; + } else { + dir = 0; + } + } + } + // Only destruct things if it is MP_CLEAR of MP_TREES + if (dir != 0) { + ret = AI_DoCommand(ctile, dir, 0, DC_AUTO | DC_NO_WATER, CMD_BUILD_ROAD); + if (CmdFailed(ret)) continue; + } + } + } + + // The tile can be connected + aystar->neighbours[aystar->num_neighbours].tile = atile; + aystar->neighbours[aystar->num_neighbours].user_data[0] = 0; + aystar->neighbours[aystar->num_neighbours++].direction = 0; + } + } + + // Next step, check for bridges and tunnels + if (current->path.parent != NULL && current->path.node.user_data[0] == 0) { + // First we get the dir from this tile and his parent + DiagDirection dir = AiNew_GetDirection(current->path.parent->node.tile, current->path.node.tile); + // It means we can only walk with the track, so the bridge has to be in the same direction + TileIndex tile = current->path.node.tile; + TileIndex new_tile = tile; + Slope tileh = GetTileSlope(tile, NULL); + + // Bridges can only be build on land that is not flat + // And if there is a road or rail blocking + if (tileh != SLOPE_FLAT || + (PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_STREET)) || + (!PathFinderInfo->rail_or_road && IsTileType(tile + TileOffsByDiagDir(dir), MP_RAILWAY))) { + for (;;) { + new_tile += TileOffsByDiagDir(dir); + + // Precheck, is the length allowed? + if (!CheckBridge_Stuff(0, GetBridgeLength(tile, new_tile))) break; + + // Check if we hit the station-tile.. we don't like that! + if (TILES_BETWEEN(new_tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) break; + + // Try building the bridge.. + ret = AI_DoCommand(tile, new_tile, (0 << 8) + (MAX_BRIDGES / 2), DC_AUTO, CMD_BUILD_BRIDGE); + if (CmdFailed(ret)) continue; + // We can build a bridge here.. add him to the neighbours + aystar->neighbours[aystar->num_neighbours].tile = new_tile; + aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_BRIDGE + (dir << 8); + aystar->neighbours[aystar->num_neighbours++].direction = 0; + // We can only have 12 neighbours, and we need 1 left for tunnels + if (aystar->num_neighbours == 11) break; + } + } + + // Next, check for tunnels! + // Tunnels can only be built on slopes corresponding to the direction + // For now, we check both sides for this tile.. terraforming gives fuzzy result + if ((dir == DIAGDIR_NE && tileh == SLOPE_NE) || + (dir == DIAGDIR_SE && tileh == SLOPE_SE) || + (dir == DIAGDIR_SW && tileh == SLOPE_SW) || + (dir == DIAGDIR_NW && tileh == SLOPE_NW)) { + // Now simply check if a tunnel can be build + ret = AI_DoCommand(tile, (PathFinderInfo->rail_or_road?0:0x200), 0, DC_AUTO, CMD_BUILD_TUNNEL); + tileh = GetTileSlope(_build_tunnel_endtile, NULL); + if (!CmdFailed(ret) && (tileh == SLOPE_SW || tileh == SLOPE_SE || tileh == SLOPE_NW || tileh == SLOPE_NE)) { + aystar->neighbours[aystar->num_neighbours].tile = _build_tunnel_endtile; + aystar->neighbours[aystar->num_neighbours].user_data[0] = AI_PATHFINDER_FLAG_TUNNEL + (dir << 8); + aystar->neighbours[aystar->num_neighbours++].direction = 0; + } + } + } +} + + +extern uint GetRailFoundation(Slope tileh, TrackBits bits); // XXX function declaration in .c +extern uint GetRoadFoundation(Slope tileh, uint bits); // XXX function declaration in .c +extern uint GetBridgeFoundation(Slope tileh, Axis); // XXX function declaration in .c +enum { + BRIDGE_NO_FOUNDATION = 1 << 0 | 1 << 3 | 1 << 6 | 1 << 9 | 1 << 12, +}; + +// The most important function: it calculates the g-value +static int32 AyStar_AiPathFinder_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent) +{ + Ai_PathFinderInfo *PathFinderInfo = (Ai_PathFinderInfo*)aystar->user_target; + int r, res = 0; + Slope tileh = GetTileSlope(current->tile, NULL); + Slope parent_tileh = GetTileSlope(parent->path.node.tile, NULL); + + // Check if we hit the end-tile + if (TILES_BETWEEN(current->tile, PathFinderInfo->end_tile_tl, PathFinderInfo->end_tile_br)) { + // We are at the end-tile, check if we had a direction or something... + if (PathFinderInfo->end_direction != AI_PATHFINDER_NO_DIRECTION && AiNew_GetDirection(current->tile, parent->path.node.tile) != PathFinderInfo->end_direction) { + // We are not pointing the right way, invalid tile + return AYSTAR_INVALID_NODE; + } + // If it was valid, drop out.. we don't build on the endtile + return 0; + } + + // Give everything a small penalty + res += AI_PATHFINDER_PENALTY; + + if (!PathFinderInfo->rail_or_road) { + // Road has the lovely advantage it can use other road... check if + // the current tile is road, and if so, give a good bonus + if (IsRoad(current->tile)) { + res -= AI_PATHFINDER_ROAD_ALREADY_EXISTS_BONUS; + } + } + + // We should give a penalty when the tile is going up or down.. this is one way to do so! + // Too bad we have to count it from the parent.. but that is not so bad. + // We also dislike long routes on slopes, since they do not look too realistic + // when there is a flat land all around, they are more expensive to build, and + // especially they essentially block the ability to connect or cross the road + // from one side. + if (parent_tileh != SLOPE_FLAT && parent->path.parent != NULL) { + // Skip if the tile was from a bridge or tunnel + if (parent->path.node.user_data[0] == 0 && current->user_data[0] == 0) { + if (PathFinderInfo->rail_or_road) { + r = GetRailFoundation(parent_tileh, 1 << AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile)); + // Maybe is BRIDGE_NO_FOUNDATION a bit strange here, but it contains just the right information.. + if (r >= 15 || (r == 0 && HASBIT(BRIDGE_NO_FOUNDATION, tileh))) { + res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; + } else { + res += AI_PATHFINDER_FOUNDATION_PENALTY; + } + } else { + if (!IsRoad(parent->path.node.tile) || !IsTileType(parent->path.node.tile, MP_TUNNELBRIDGE)) { + r = GetRoadFoundation(parent_tileh, AiNew_GetRoadDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile)); + if (r >= 15 || r == 0) { + res += AI_PATHFINDER_TILE_GOES_UP_PENALTY; + } else { + res += AI_PATHFINDER_FOUNDATION_PENALTY; + } + } + } + } + } + + // Are we part of a tunnel? + if ((AI_PATHFINDER_FLAG_TUNNEL & current->user_data[0]) != 0) { + // Tunnels are very expensive when build on long routes.. + // Ironicly, we are using BridgeCode here ;) + r = AI_PATHFINDER_TUNNEL_PENALTY * GetBridgeLength(current->tile, parent->path.node.tile); + res += r + (r >> 8); + } + + // Are we part of a bridge? + if ((AI_PATHFINDER_FLAG_BRIDGE & current->user_data[0]) != 0) { + // That means for every length a penalty + res += AI_PATHFINDER_BRIDGE_PENALTY * GetBridgeLength(current->tile, parent->path.node.tile); + // Check if we are going up or down, first for the starting point + // In user_data[0] is at the 8th bit the direction + if (!HASBIT(BRIDGE_NO_FOUNDATION, parent_tileh)) { + if (GetBridgeFoundation(parent_tileh, (current->user_data[0] >> 8) & 1) < 15) { + res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; + } + } + // Second for the end point + if (!HASBIT(BRIDGE_NO_FOUNDATION, tileh)) { + if (GetBridgeFoundation(tileh, (current->user_data[0] >> 8) & 1) < 15) { + res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; + } + } + if (parent_tileh == SLOPE_FLAT) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; + if (tileh == SLOPE_FLAT) res += AI_PATHFINDER_BRIDGE_GOES_UP_PENALTY; + } + + // To prevent the AI from taking the fastest way in tiles, but not the fastest way + // in speed, we have to give a good penalty to direction changing + // This way, we get almost the fastest way in tiles, and a very good speed on the track + if (!PathFinderInfo->rail_or_road) { + if (parent->path.parent != NULL && + AiNew_GetDirection(current->tile, parent->path.node.tile) != AiNew_GetDirection(parent->path.node.tile, parent->path.parent->node.tile)) { + // When road exists, we don't like turning, but its free, so don't be to piggy about it + if (IsRoad(parent->path.node.tile)) { + res += AI_PATHFINDER_DIRECTION_CHANGE_ON_EXISTING_ROAD_PENALTY; + } else { + res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; + } + } + } else { + // For rail we have 1 exeption: diagonal rail.. + // So we fetch 2 raildirection. That of the current one, and of the one before that + if (parent->path.parent != NULL && parent->path.parent->parent != NULL) { + int dir1 = AiNew_GetRailDirection(parent->path.parent->node.tile, parent->path.node.tile, current->tile); + int dir2 = AiNew_GetRailDirection(parent->path.parent->parent->node.tile, parent->path.parent->node.tile, parent->path.node.tile); + // First, see if we are on diagonal path, that is better than straight path + if (dir1 > 1) res -= AI_PATHFINDER_DIAGONAL_BONUS; + + // First see if they are different + if (dir1 != dir2) { + // dir 2 and 3 are 1 diagonal track, and 4 and 5. + if (!(((dir1 == 2 || dir1 == 3) && (dir2 == 2 || dir2 == 3)) || ((dir1 == 4 || dir1 == 5) && (dir2 == 4 || dir2 == 5)))) { + // It is not, so we changed of direction + res += AI_PATHFINDER_DIRECTION_CHANGE_PENALTY; + } + if (parent->path.parent->parent->parent != NULL) { + int dir3 = AiNew_GetRailDirection(parent->path.parent->parent->parent->node.tile, parent->path.parent->parent->node.tile, parent->path.parent->node.tile); + // Check if we changed 3 tiles of direction in 3 tiles.. bad!!! + if ((dir1 == 0 || dir1 == 1) && dir2 > 1 && (dir3 == 0 || dir3 == 1)) { + res += AI_PATHFINDER_CURVE_PENALTY; + } + } + } + } + } + + return (res < 0) ? 0 : res; +} diff --git a/src/ai/trolly/shared.c b/src/ai/trolly/shared.c deleted file mode 100644 --- a/src/ai/trolly/shared.c +++ /dev/null @@ -1,118 +0,0 @@ -/* $Id$ */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../map.h" -#include "trolly.h" -#include "../../vehicle.h" - -int AiNew_GetRailDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) -{ - // 0 = vert - // 1 = horz - // 2 = dig up-left - // 3 = dig down-right - // 4 = dig down-left - // 5 = dig up-right - - uint x1 = TileX(tile_a); - uint x2 = TileX(tile_b); - uint x3 = TileX(tile_c); - - uint y1 = TileY(tile_a); - uint y2 = TileY(tile_b); - uint y3 = TileY(tile_c); - - if (y1 == y2 && y2 == y3) return 0; - if (x1 == x2 && x2 == x3) return 1; - if (y2 > y1) return x2 > x3 ? 2 : 4; - if (x2 > x1) return y2 > y3 ? 2 : 5; - if (y1 > y2) return x2 > x3 ? 5 : 3; - if (x1 > x2) return y2 > y3 ? 4 : 3; - - return 0; -} - -int AiNew_GetRoadDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) -{ - int x1, x2, x3; - int y1, y2, y3; - int r; - - x1 = TileX(tile_a); - x2 = TileX(tile_b); - x3 = TileX(tile_c); - - y1 = TileY(tile_a); - y2 = TileY(tile_b); - y3 = TileY(tile_c); - - r = 0; - - if (x1 < x2) r += 8; - if (y1 < y2) r += 1; - if (x1 > x2) r += 2; - if (y1 > y2) r += 4; - - if (x2 < x3) r += 2; - if (y2 < y3) r += 4; - if (x2 > x3) r += 8; - if (y2 > y3) r += 1; - - return r; -} - -// Get's the direction between 2 tiles seen from tile_a -DiagDirection AiNew_GetDirection(TileIndex tile_a, TileIndex tile_b) -{ - if (TileY(tile_a) < TileY(tile_b)) return DIAGDIR_SE; - if (TileY(tile_a) > TileY(tile_b)) return DIAGDIR_NW; - if (TileX(tile_a) < TileX(tile_b)) return DIAGDIR_SW; - return DIAGDIR_NE; -} - - -// This functions looks up if this vehicle is special for this AI -// and returns his flag -uint AiNew_GetSpecialVehicleFlag(Player* p, Vehicle* v) -{ - uint i; - - for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { - if (p->ainew.special_vehicles[i].veh_id == v->index) { - return p->ainew.special_vehicles[i].flag; - } - } - - // Not found :( - return 0; -} - - -bool AiNew_SetSpecialVehicleFlag(Player* p, Vehicle* v, uint flag) -{ - int new_id = -1; - uint i; - - for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { - if (p->ainew.special_vehicles[i].veh_id == v->index) { - p->ainew.special_vehicles[i].flag |= flag; - return true; - } - if (new_id == -1 && - p->ainew.special_vehicles[i].veh_id == 0 && - p->ainew.special_vehicles[i].flag == 0) { - new_id = i; - } - } - - // Out of special_vehicle spots :s - if (new_id == -1) { - DEBUG(ai, 1, "special_vehicles list is too small"); - return false; - } - p->ainew.special_vehicles[new_id].veh_id = v->index; - p->ainew.special_vehicles[new_id].flag = flag; - return true; -} diff --git a/src/ai/trolly/shared.cpp b/src/ai/trolly/shared.cpp new file mode 100644 --- /dev/null +++ b/src/ai/trolly/shared.cpp @@ -0,0 +1,118 @@ +/* $Id$ */ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../debug.h" +#include "../../map.h" +#include "trolly.h" +#include "../../vehicle.h" + +int AiNew_GetRailDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) +{ + // 0 = vert + // 1 = horz + // 2 = dig up-left + // 3 = dig down-right + // 4 = dig down-left + // 5 = dig up-right + + uint x1 = TileX(tile_a); + uint x2 = TileX(tile_b); + uint x3 = TileX(tile_c); + + uint y1 = TileY(tile_a); + uint y2 = TileY(tile_b); + uint y3 = TileY(tile_c); + + if (y1 == y2 && y2 == y3) return 0; + if (x1 == x2 && x2 == x3) return 1; + if (y2 > y1) return x2 > x3 ? 2 : 4; + if (x2 > x1) return y2 > y3 ? 2 : 5; + if (y1 > y2) return x2 > x3 ? 5 : 3; + if (x1 > x2) return y2 > y3 ? 4 : 3; + + return 0; +} + +int AiNew_GetRoadDirection(TileIndex tile_a, TileIndex tile_b, TileIndex tile_c) +{ + int x1, x2, x3; + int y1, y2, y3; + int r; + + x1 = TileX(tile_a); + x2 = TileX(tile_b); + x3 = TileX(tile_c); + + y1 = TileY(tile_a); + y2 = TileY(tile_b); + y3 = TileY(tile_c); + + r = 0; + + if (x1 < x2) r += 8; + if (y1 < y2) r += 1; + if (x1 > x2) r += 2; + if (y1 > y2) r += 4; + + if (x2 < x3) r += 2; + if (y2 < y3) r += 4; + if (x2 > x3) r += 8; + if (y2 > y3) r += 1; + + return r; +} + +// Get's the direction between 2 tiles seen from tile_a +DiagDirection AiNew_GetDirection(TileIndex tile_a, TileIndex tile_b) +{ + if (TileY(tile_a) < TileY(tile_b)) return DIAGDIR_SE; + if (TileY(tile_a) > TileY(tile_b)) return DIAGDIR_NW; + if (TileX(tile_a) < TileX(tile_b)) return DIAGDIR_SW; + return DIAGDIR_NE; +} + + +// This functions looks up if this vehicle is special for this AI +// and returns his flag +uint AiNew_GetSpecialVehicleFlag(Player* p, Vehicle* v) +{ + uint i; + + for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { + if (p->ainew.special_vehicles[i].veh_id == v->index) { + return p->ainew.special_vehicles[i].flag; + } + } + + // Not found :( + return 0; +} + + +bool AiNew_SetSpecialVehicleFlag(Player* p, Vehicle* v, uint flag) +{ + int new_id = -1; + uint i; + + for (i = 0; i < AI_MAX_SPECIAL_VEHICLES; i++) { + if (p->ainew.special_vehicles[i].veh_id == v->index) { + p->ainew.special_vehicles[i].flag |= flag; + return true; + } + if (new_id == -1 && + p->ainew.special_vehicles[i].veh_id == 0 && + p->ainew.special_vehicles[i].flag == 0) { + new_id = i; + } + } + + // Out of special_vehicle spots :s + if (new_id == -1) { + DEBUG(ai, 1, "special_vehicles list is too small"); + return false; + } + p->ainew.special_vehicles[new_id].veh_id = v->index; + p->ainew.special_vehicles[new_id].flag = flag; + return true; +} diff --git a/src/ai/trolly/trolly.c b/src/ai/trolly/trolly.c deleted file mode 100644 --- a/src/ai/trolly/trolly.c +++ /dev/null @@ -1,1353 +0,0 @@ -/* $Id$ */ - -/* - * This AI was created as a direct reaction to the big demand for some good AIs - * in OTTD. Too bad it never left alpha-stage, and it is considered dead in its - * current form. - * By the time of writing this, we, the creator of this AI and a good friend of - * mine, are designing a whole new AI-system that allows us to create AIs - * easier and without all the fuzz we encountered while I was working on this - * AI. By the time that system is finished, you can expect that this AI will - * dissapear, because it is pretty obselete and bad programmed. - * - * Meanwhile I wish you all much fun with this AI; if you are interested as - * AI-developer in this AI, I advise you not stare too long to some code, some - * things in here really are... strange ;) But in either way: enjoy :) - * - * -- TrueLight :: 2005-09-01 - */ - -#include "../../stdafx.h" -#include "../../openttd.h" -#include "../../debug.h" -#include "../../functions.h" -#include "../../road_map.h" -#include "../../station_map.h" -#include "table/strings.h" -#include "../../map.h" -#include "../../tile.h" -#include "../../command.h" -#include "trolly.h" -#include "../../town.h" -#include "../../industry.h" -#include "../../station.h" -#include "../../engine.h" -#include "../../gui.h" -#include "../../depot.h" -#include "../../vehicle.h" -#include "../../date.h" -#include "../ai.h" - -// This function is called after StartUp. It is the init of an AI -static void AiNew_State_FirstTime(Player *p) -{ - // This assert is used to protect those function from misuse - // You have quickly a small mistake in the state-array - // With that, everything would go wrong. Finding that, is almost impossible - // With this assert, that problem can never happen. - assert(p->ainew.state == AI_STATE_FIRST_TIME); - // We first have to init some things - - if (_current_player == 1) ShowErrorMessage(INVALID_STRING_ID, TEMP_AI_IN_PROGRESS, 0, 0); - - // The PathFinder (AyStar) - // TODO: Maybe when an AI goes bankrupt, this is de-init - // or when coming from a savegame.. should be checked out! - p->ainew.path_info.start_tile_tl = 0; - p->ainew.path_info.start_tile_br = 0; - p->ainew.path_info.end_tile_tl = 0; - p->ainew.path_info.end_tile_br = 0; - p->ainew.pathfinder = new_AyStar_AiPathFinder(12, &p->ainew.path_info); - - p->ainew.idle = 0; - p->ainew.last_vehiclecheck_date = _date; - - // We ALWAYS start with a bus route.. just some basic money ;) - p->ainew.action = AI_ACTION_BUS_ROUTE; - - // Let's popup the news, and after that, start building.. - p->ainew.state = AI_STATE_WAKE_UP; -} - - -// This function just waste some time -// It keeps it more real. The AI can build on such tempo no normal user -// can ever keep up with that. The competitor_speed already delays a bit -// but after the AI finished a track it really needs to go to sleep. -// -// Let's say, we sleep between one and three days if the AI is put on Very Fast. -// This means that on Very Slow it will be between 16 and 48 days.. slow enough? -static void AiNew_State_Nothing(Player *p) -{ - assert(p->ainew.state == AI_STATE_NOTHING); - // If we are done idling, start over again - if (p->ainew.idle == 0) p->ainew.idle = AI_RandomRange(DAY_TICKS * 2) + DAY_TICKS; - if (--p->ainew.idle == 0) { - // We are done idling.. what you say? Let's do something! - // I mean.. the next tick ;) - p->ainew.state = AI_STATE_WAKE_UP; - } -} - - -// This function picks out a task we are going to do. -// Currently supported: -// - Make new route -// - Check route -// - Build HQ -static void AiNew_State_WakeUp(Player *p) -{ - int32 money; - int c; - assert(p->ainew.state == AI_STATE_WAKE_UP); - // First, check if we have a HQ - if (p->location_of_house == 0) { - // We have no HQ yet, build one on a random place - // Random till we found a place for it! - // TODO: this should not be on a random place.. - AiNew_Build_CompanyHQ(p, AI_Random() % MapSize()); - // Enough for now, but we want to come back here the next time - // so we do not change any status - return; - } - - money = p->player_money - AI_MINIMUM_MONEY; - - // Let's pick an action! - if (p->ainew.action == AI_ACTION_NONE) { - c = AI_Random() & 0xFF; - if (p->current_loan > 0 && - p->old_economy[1].income > AI_MINIMUM_INCOME_FOR_LOAN && - c < 10) { - p->ainew.action = AI_ACTION_REPAY_LOAN; - } else if (p->ainew.last_vehiclecheck_date + AI_DAYS_BETWEEN_VEHICLE_CHECKS < _date) { - // Check all vehicles once in a while - p->ainew.action = AI_ACTION_CHECK_ALL_VEHICLES; - p->ainew.last_vehiclecheck_date = _date; - } else if (c < 100 && !_patches.ai_disable_veh_roadveh) { - // Do we have any spots for road-vehicles left open? - if (GetFreeUnitNumber(VEH_Road) <= _patches.max_roadveh) { - if (c < 85) { - p->ainew.action = AI_ACTION_TRUCK_ROUTE; - } else { - p->ainew.action = AI_ACTION_BUS_ROUTE; - } - } -#if 0 - } else if (c < 200 && !_patches.ai_disable_veh_train) { - if (GetFreeUnitNumber(VEH_Train) <= _patches.max_trains) { - p->ainew.action = AI_ACTION_TRAIN_ROUTE; - } -#endif - } - - p->ainew.counter = 0; - } - - if (p->ainew.counter++ > AI_MAX_TRIES_FOR_SAME_ROUTE) { - p->ainew.action = AI_ACTION_NONE; - return; - } - - if (_patches.ai_disable_veh_roadveh && ( - p->ainew.action == AI_ACTION_BUS_ROUTE || - p->ainew.action == AI_ACTION_TRUCK_ROUTE - )) { - p->ainew.action = AI_ACTION_NONE; - return; - } - - if (p->ainew.action == AI_ACTION_REPAY_LOAN && - money > AI_MINIMUM_LOAN_REPAY_MONEY) { - // We start repaying some money.. - p->ainew.state = AI_STATE_REPAY_MONEY; - return; - } - - if (p->ainew.action == AI_ACTION_CHECK_ALL_VEHICLES) { - p->ainew.state = AI_STATE_CHECK_ALL_VEHICLES; - return; - } - - // It is useless to start finding a route if we don't have enough money - // to build the route anyway.. - if (p->ainew.action == AI_ACTION_BUS_ROUTE && - money > AI_MINIMUM_BUS_ROUTE_MONEY) { - if (GetFreeUnitNumber(VEH_Road) > _patches.max_roadveh) { - p->ainew.action = AI_ACTION_NONE; - return; - } - p->ainew.cargo = AI_NEED_CARGO; - p->ainew.state = AI_STATE_LOCATE_ROUTE; - p->ainew.tbt = AI_BUS; // Bus-route - return; - } - if (p->ainew.action == AI_ACTION_TRUCK_ROUTE && - money > AI_MINIMUM_TRUCK_ROUTE_MONEY) { - if (GetFreeUnitNumber(VEH_Road) > _patches.max_roadveh) { - p->ainew.action = AI_ACTION_NONE; - return; - } - p->ainew.cargo = AI_NEED_CARGO; - p->ainew.last_id = 0; - p->ainew.state = AI_STATE_LOCATE_ROUTE; - p->ainew.tbt = AI_TRUCK; - return; - } - - p->ainew.state = AI_STATE_NOTHING; -} - - -static void AiNew_State_ActionDone(Player *p) -{ - p->ainew.action = AI_ACTION_NONE; - p->ainew.state = AI_STATE_NOTHING; -} - - -// Check if a city or industry is good enough to start a route there -static bool AiNew_Check_City_or_Industry(Player *p, int ic, byte type) -{ - if (type == AI_CITY) { - const Town* t = GetTown(ic); - const Station* st; - uint count = 0; - int j = 0; - - // We don't like roadconstructions, don't even true such a city - if (t->road_build_months != 0) return false; - - // Check if the rating in a city is high enough - // If not, take a chance if we want to continue - if (t->ratings[_current_player] < 0 && AI_CHANCE16(1,4)) return false; - - if (t->max_pass - t->act_pass < AI_CHECKCITY_NEEDED_CARGO && !AI_CHANCE16(1,AI_CHECKCITY_CITY_CHANCE)) return false; - - // Check if we have build a station in this town the last 6 months - // else we don't do it. This is done, because stat updates can be slow - // and sometimes it takes up to 4 months before the stats are corectly. - // This way we don't get 12 busstations in one city of 100 population ;) - FOR_ALL_STATIONS(st) { - // Do we own it? - if (st->owner == _current_player) { - // Are we talking busses? - if (p->ainew.tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) != FACIL_BUS_STOP) continue; - // Is it the same city as we are in now? - if (st->town != t) continue; - // When was this station build? - if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; - // Cound the amount of stations in this city that we own - count++; - } else { - // We do not own it, request some info about the station - // we want to know if this station gets the same good. If so, - // we want to know its rating. If it is too high, we are not going - // to build there - if (!st->goods[CT_PASSENGERS].last_speed) continue; - // Is it around our city - if (DistanceManhattan(st->xy, t->xy) > 10) continue; - // It does take this cargo.. what is his rating? - if (st->goods[CT_PASSENGERS].rating < AI_CHECKCITY_CARGO_RATING) continue; - j++; - // When this is the first station, we build a second with no problem ;) - if (j == 1) continue; - // The rating is high.. second station... - // a little chance that we still continue - // But if there are 3 stations of this size, we never go on... - if (j == 2 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; - // We don't like this station :( - return false; - } - } - - // We are about to add one... - count++; - // Check if we the city can provide enough cargo for this amount of stations.. - if (count * AI_CHECKCITY_CARGO_PER_STATION > t->max_pass) return false; - - // All check are okay, so we can build here! - return true; - } - if (type == AI_INDUSTRY) { - const Industry* i = GetIndustry(ic); - const Station* st; - int count = 0; - int j = 0; - - if (i->town != NULL && i->town->ratings[_current_player] < 0 && AI_CHANCE16(1,4)) return false; - - // No limits on delevering stations! - // Or for industry that does not give anything yet - if (i->produced_cargo[0] == CT_INVALID || i->total_production[0] == 0) return true; - - if (i->total_production[0] - i->total_transported[0] < AI_CHECKCITY_NEEDED_CARGO) return false; - - // Check if we have build a station in this town the last 6 months - // else we don't do it. This is done, because stat updates can be slow - // and sometimes it takes up to 4 months before the stats are corectly. - FOR_ALL_STATIONS(st) { - // Do we own it? - if (st->owner == _current_player) { - // Are we talking trucks? - if (p->ainew.tbt == AI_TRUCK && (FACIL_TRUCK_STOP & st->facilities) != FACIL_TRUCK_STOP) continue; - // Is it the same city as we are in now? - if (st->town != i->town) continue; - // When was this station build? - if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; - // Cound the amount of stations in this city that we own - count++; - } else { - // We do not own it, request some info about the station - // we want to know if this station gets the same good. If so, - // we want to know its rating. If it is too high, we are not going - // to build there - if (i->produced_cargo[0] == CT_INVALID) continue; - // It does not take this cargo - if (!st->goods[i->produced_cargo[0]].last_speed) continue; - // Is it around our industry - if (DistanceManhattan(st->xy, i->xy) > 5) continue; - // It does take this cargo.. what is his rating? - if (st->goods[i->produced_cargo[0]].rating < AI_CHECKCITY_CARGO_RATING) continue; - j++; - // The rating is high.. a little chance that we still continue - // But if there are 2 stations of this size, we never go on... - if (j == 1 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; - // We don't like this station :( - return false; - } - } - - // We are about to add one... - count++; - // Check if we the city can provide enough cargo for this amount of stations.. - if (count * AI_CHECKCITY_CARGO_PER_STATION > i->total_production[0]) return false; - - // All check are okay, so we can build here! - return true; - } - - return true; -} - - -// This functions tries to locate a good route -static void AiNew_State_LocateRoute(Player *p) -{ - assert(p->ainew.state == AI_STATE_LOCATE_ROUTE); - // For now, we only support PASSENGERS, CITY and BUSSES - - // We don't have a route yet - if (p->ainew.cargo == AI_NEED_CARGO) { - p->ainew.new_cost = 0; // No cost yet - p->ainew.temp = -1; - // Reset the counter - p->ainew.counter = 0; - - p->ainew.from_ic = -1; - p->ainew.to_ic = -1; - if (p->ainew.tbt == AI_BUS) { - // For now we only have a passenger route - p->ainew.cargo = CT_PASSENGERS; - - // Find a route to cities - p->ainew.from_type = AI_CITY; - p->ainew.to_type = AI_CITY; - } else if (p->ainew.tbt == AI_TRUCK) { - p->ainew.cargo = AI_NO_CARGO; - - p->ainew.from_type = AI_INDUSTRY; - p->ainew.to_type = AI_INDUSTRY; - } - - // Now we are doing initing, we wait one tick - return; - } - - // Increase the counter and abort if it is taking too long! - p->ainew.counter++; - if (p->ainew.counter > AI_LOCATE_ROUTE_MAX_COUNTER) { - // Switch back to doing nothing! - p->ainew.state = AI_STATE_NOTHING; - return; - } - - // We are going to locate a city from where we are going to connect - if (p->ainew.from_ic == -1) { - if (p->ainew.temp == -1) { - // First, we pick a random spot to search from - if (p->ainew.from_type == AI_CITY) { - p->ainew.temp = AI_RandomRange(GetMaxTownIndex() + 1); - } else { - p->ainew.temp = AI_RandomRange(GetMaxIndustryIndex() + 1); - } - } - - if (!AiNew_Check_City_or_Industry(p, p->ainew.temp, p->ainew.from_type)) { - // It was not a valid city - // increase the temp with one, and return. We will come back later here - // to try again - p->ainew.temp++; - if (p->ainew.from_type == AI_CITY) { - if (p->ainew.temp > GetMaxTownIndex()) p->ainew.temp = 0; - } else { - if (p->ainew.temp > GetMaxIndustryIndex()) p->ainew.temp = 0; - } - - // Don't do an attempt if we are trying the same id as the last time... - if (p->ainew.last_id == p->ainew.temp) return; - p->ainew.last_id = p->ainew.temp; - - return; - } - - // We found a good city/industry, save the data of it - p->ainew.from_ic = p->ainew.temp; - - // Start the next tick with finding a to-city - p->ainew.temp = -1; - return; - } - - // Find a to-city - if (p->ainew.temp == -1) { - // First, we pick a random spot to search to - if (p->ainew.to_type == AI_CITY) { - p->ainew.temp = AI_RandomRange(GetMaxTownIndex() + 1); - } else { - p->ainew.temp = AI_RandomRange(GetMaxIndustryIndex() + 1); - } - } - - // The same city is not allowed - // Also check if the city is valid - if (p->ainew.temp != p->ainew.from_ic && AiNew_Check_City_or_Industry(p, p->ainew.temp, p->ainew.to_type)) { - // Maybe it is valid.. - - /* We need to know if they are not to far apart from eachother.. - * We do that by checking how much cargo we have to move and how long the - * route is. - */ - - if (p->ainew.from_type == AI_CITY && p->ainew.tbt == AI_BUS) { - const Town* town_from = GetTown(p->ainew.from_ic); - const Town* town_temp = GetTown(p->ainew.temp); - uint distance = DistanceManhattan(town_from->xy, town_temp->xy); - int max_cargo; - - max_cargo = town_from->max_pass + town_temp->max_pass; - max_cargo -= town_from->act_pass + town_temp->act_pass; - - // max_cargo is now the amount of cargo we can move between the two cities - // If it is more than the distance, we allow it - if (distance <= max_cargo * AI_LOCATEROUTE_BUS_CARGO_DISTANCE) { - // We found a good city/industry, save the data of it - p->ainew.to_ic = p->ainew.temp; - p->ainew.state = AI_STATE_FIND_STATION; - - DEBUG(ai, 1, "[LocateRoute] found bus-route of %d tiles long (from %d to %d)", - distance, - p->ainew.from_ic, - p->ainew.temp - ); - - p->ainew.from_tile = 0; - p->ainew.to_tile = 0; - - return; - } - } else if (p->ainew.tbt == AI_TRUCK) { - const Industry* ind_from = GetIndustry(p->ainew.from_ic); - const Industry* ind_temp = GetIndustry(p->ainew.temp); - bool found = false; - int max_cargo = 0; - uint i; - - // TODO: in max_cargo, also check other cargo (beside [0]) - // First we check if the from_ic produces cargo that this ic accepts - if (ind_from->produced_cargo[0] != CT_INVALID && ind_from->total_production[0] != 0) { - for (i = 0; i < lengthof(ind_temp->accepts_cargo); i++) { - if (ind_temp->accepts_cargo[i] == CT_INVALID) break; - if (ind_from->produced_cargo[0] == ind_temp->accepts_cargo[i]) { - // Found a compatible industry - max_cargo = ind_from->total_production[0] - ind_from->total_transported[0]; - found = true; - p->ainew.from_deliver = true; - p->ainew.to_deliver = false; - break; - } - } - } - if (!found && ind_temp->produced_cargo[0] != CT_INVALID && ind_temp->total_production[0] != 0) { - // If not check if the current ic produces cargo that the from_ic accepts - for (i = 0; i < lengthof(ind_from->accepts_cargo); i++) { - if (ind_from->accepts_cargo[i] == CT_INVALID) break; - if (ind_temp->produced_cargo[0] == ind_from->accepts_cargo[i]) { - // Found a compatbiel industry - found = true; - max_cargo = ind_temp->total_production[0] - ind_temp->total_transported[0]; - p->ainew.from_deliver = false; - p->ainew.to_deliver = true; - break; - } - } - } - if (found) { - // Yeah, they are compatible!!! - // Check the length against the amount of goods - uint distance = DistanceManhattan(ind_from->xy, ind_temp->xy); - - if (distance > AI_LOCATEROUTE_TRUCK_MIN_DISTANCE && - distance <= max_cargo * AI_LOCATEROUTE_TRUCK_CARGO_DISTANCE) { - p->ainew.to_ic = p->ainew.temp; - if (p->ainew.from_deliver) { - p->ainew.cargo = ind_from->produced_cargo[0]; - } else { - p->ainew.cargo = ind_temp->produced_cargo[0]; - } - p->ainew.state = AI_STATE_FIND_STATION; - - DEBUG(ai, 1, "[LocateRoute] found truck-route of %d tiles long (from %d to %d)", - distance, - p->ainew.from_ic, - p->ainew.temp - ); - - p->ainew.from_tile = 0; - p->ainew.to_tile = 0; - - return; - } - } - } - } - - // It was not a valid city - // increase the temp with one, and return. We will come back later here - // to try again - p->ainew.temp++; - if (p->ainew.to_type == AI_CITY) { - if (p->ainew.temp > GetMaxTownIndex()) p->ainew.temp = 0; - } else { - if (p->ainew.temp > GetMaxIndustryIndex()) p->ainew.temp = 0; - } - - // Don't do an attempt if we are trying the same id as the last time... - if (p->ainew.last_id == p->ainew.temp) return; - p->ainew.last_id = p->ainew.temp; -} - - -// Check if there are not more than a certain amount of vehicles pointed to a certain -// station. This to prevent 10 busses going to one station, which gives... problems ;) -static bool AiNew_CheckVehicleStation(Player *p, Station *st) -{ - int count = 0; - Vehicle *v; - - // Also check if we don't have already a lot of busses to this city... - FOR_ALL_VEHICLES(v) { - if (v->owner == _current_player) { - const Order *order; - - FOR_VEHICLE_ORDERS(v, order) { - if (order->type == OT_GOTO_STATION && GetStation(order->dest) == st) { - // This vehicle has this city in its list - count++; - } - } - } - } - - if (count > AI_CHECK_MAX_VEHICLE_PER_STATION) return false; - return true; -} - -// This function finds a good spot for a station -static void AiNew_State_FindStation(Player *p) -{ - TileIndex tile; - Station *st; - int count = 0; - EngineID i; - TileIndex new_tile = 0; - byte direction = 0; - Town *town = NULL; - assert(p->ainew.state == AI_STATE_FIND_STATION); - - if (p->ainew.from_tile == 0) { - // First we scan for a station in the from-city - if (p->ainew.from_type == AI_CITY) { - town = GetTown(p->ainew.from_ic); - tile = town->xy; - } else { - tile = GetIndustry(p->ainew.from_ic)->xy; - } - } else if (p->ainew.to_tile == 0) { - // Second we scan for a station in the to-city - if (p->ainew.to_type == AI_CITY) { - town = GetTown(p->ainew.to_ic); - tile = town->xy; - } else { - tile = GetIndustry(p->ainew.to_ic)->xy; - } - } else { - // Unsupported request - // Go to FIND_PATH - p->ainew.temp = -1; - p->ainew.state = AI_STATE_FIND_PATH; - return; - } - - // First, we are going to look at the stations that already exist inside the city - // If there is enough cargo left in the station, we take that station - // If that is not possible, and there are more than 2 stations in the city, abort - i = AiNew_PickVehicle(p); - // Euhmz, this should not happen _EVER_ - // Quit finding a route... - if (i == INVALID_ENGINE) { - p->ainew.state = AI_STATE_NOTHING; - return; - } - - FOR_ALL_STATIONS(st) { - if (st->owner == _current_player) { - if (p->ainew.tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) == FACIL_BUS_STOP) { - if (st->town == town) { - // Check how much cargo there is left in the station - if ((st->goods[p->ainew.cargo].waiting_acceptance & 0xFFF) > RoadVehInfo(i)->capacity * AI_STATION_REUSE_MULTIPLER) { - if (AiNew_CheckVehicleStation(p, st)) { - // We did found a station that was good enough! - new_tile = st->xy; - direction = GetRoadStopDir(st->xy); - break; - } - } - count++; - } - } - } - } - // We are going to add a new station... - if (new_tile == 0) count++; - // No more than 2 stations allowed in a city - // This is because only the best 2 stations of one cargo do get any cargo - if (count > 2) { - p->ainew.state = AI_STATE_NOTHING; - return; - } - - if (new_tile == 0 && p->ainew.tbt == AI_BUS) { - uint x, y, i = 0; - int r; - uint best; - uint accepts[NUM_CARGO]; - TileIndex found_spot[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE*4]; - uint found_best[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE*4]; - // To find a good spot we scan a range from the center, a get the point - // where we get the most cargo and where it is buildable. - // TODO: also check for station of myself and make sure we are not - // taking eachothers passangers away (bad result when it does not) - for (x = TileX(tile) - AI_FINDSTATION_TILE_RANGE; x <= TileX(tile) + AI_FINDSTATION_TILE_RANGE; x++) { - for (y = TileY(tile) - AI_FINDSTATION_TILE_RANGE; y <= TileY(tile) + AI_FINDSTATION_TILE_RANGE; y++) { - new_tile = TileXY(x, y); - if (IsTileType(new_tile, MP_CLEAR) || IsTileType(new_tile, MP_TREES)) { - // This tile we can build on! - // Check acceptance - // XXX - Get the catchment area - GetAcceptanceAroundTiles(accepts, new_tile, 1, 1, 4); - // >> 3 == 0 means no cargo - if (accepts[p->ainew.cargo] >> 3 == 0) continue; - // See if we can build the station - r = AiNew_Build_Station(p, p->ainew.tbt, new_tile, 0, 0, 0, DC_QUERY_COST); - if (CmdFailed(r)) continue; - // We can build it, so add it to found_spot - found_spot[i] = new_tile; - found_best[i++] = accepts[p->ainew.cargo]; - } - } - } - - // If i is still zero, we did not find anything - if (i == 0) { - p->ainew.state = AI_STATE_NOTHING; - return; - } - - // Go through all the found_best and check which has the highest value - best = 0; - new_tile = 0; - - for (x = 0; x < i; x++) { - if (found_best[x] > best || - (found_best[x] == best && DistanceManhattan(tile, new_tile) > DistanceManhattan(tile, found_spot[x]))) { - new_tile = found_spot[x]; - best = found_best[x]; - } - } - - // See how much it is going to cost us... - r = AiNew_Build_Station(p, p->ainew.tbt, new_tile, 0, 0, 0, DC_QUERY_COST); - p->ainew.new_cost += r; - - direction = AI_PATHFINDER_NO_DIRECTION; - } else if (new_tile == 0 && p->ainew.tbt == AI_TRUCK) { - // Truck station locater works differently.. a station can be on any place - // as long as it is in range. So we give back code AI_STATION_RANGE - // so the pathfinder routine can work it out! - new_tile = AI_STATION_RANGE; - direction = AI_PATHFINDER_NO_DIRECTION; - } - - if (p->ainew.from_tile == 0) { - p->ainew.from_tile = new_tile; - p->ainew.from_direction = direction; - // Now we found thisone, go in for to_tile - return; - } else if (p->ainew.to_tile == 0) { - p->ainew.to_tile = new_tile; - p->ainew.to_direction = direction; - // K, done placing stations! - p->ainew.temp = -1; - p->ainew.state = AI_STATE_FIND_PATH; - return; - } -} - - -// We try to find a path between 2 points -static void AiNew_State_FindPath(Player *p) -{ - int r; - assert(p->ainew.state == AI_STATE_FIND_PATH); - - // First time, init some data - if (p->ainew.temp == -1) { - // Init path_info - if (p->ainew.from_tile == AI_STATION_RANGE) { - const Industry* i = GetIndustry(p->ainew.from_ic); - - // For truck routes we take a range around the industry - p->ainew.path_info.start_tile_tl = i->xy - TileDiffXY(1, 1); - p->ainew.path_info.start_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); - p->ainew.path_info.start_direction = p->ainew.from_direction; - } else { - p->ainew.path_info.start_tile_tl = p->ainew.from_tile; - p->ainew.path_info.start_tile_br = p->ainew.from_tile; - p->ainew.path_info.start_direction = p->ainew.from_direction; - } - - if (p->ainew.to_tile == AI_STATION_RANGE) { - const Industry* i = GetIndustry(p->ainew.to_ic); - - p->ainew.path_info.end_tile_tl = i->xy - TileDiffXY(1, 1); - p->ainew.path_info.end_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); - p->ainew.path_info.end_direction = p->ainew.to_direction; - } else { - p->ainew.path_info.end_tile_tl = p->ainew.to_tile; - p->ainew.path_info.end_tile_br = p->ainew.to_tile; - p->ainew.path_info.end_direction = p->ainew.to_direction; - } - - p->ainew.path_info.rail_or_road = (p->ainew.tbt == AI_TRAIN); - - // First, clean the pathfinder with our new begin and endpoints - clean_AyStar_AiPathFinder(p->ainew.pathfinder, &p->ainew.path_info); - - p->ainew.temp = 0; - } - - // Start the pathfinder - r = p->ainew.pathfinder->main(p->ainew.pathfinder); - switch (r) { - case AYSTAR_NO_PATH: - DEBUG(ai, 1, "No route found by pathfinder"); - // Start all over again - p->ainew.state = AI_STATE_NOTHING; - break; - - case AYSTAR_FOUND_END_NODE: // We found the end-point - p->ainew.temp = -1; - p->ainew.state = AI_STATE_FIND_DEPOT; - break; - - // In any other case, we are still busy finding the route - default: break; - } -} - - -// This function tries to locate a good place for a depot! -static void AiNew_State_FindDepot(Player *p) -{ - // To place the depot, we walk through the route, and if we find a lovely spot (MP_CLEAR, MP_TREES), we place it there.. - // Simple, easy, works! - // To make the depot stand in the middle of the route, we start from the center.. - // But first we walk through the route see if we can find a depot that is ours - // this keeps things nice ;) - int g, i, r; - DiagDirection j; - TileIndex tile; - assert(p->ainew.state == AI_STATE_FIND_DEPOT); - - p->ainew.depot_tile = 0; - - for (i=2;iainew.path_info.route_length-2;i++) { - tile = p->ainew.path_info.route[i]; - for (j = 0; j < 4; j++) { - TileIndex t = tile + TileOffsByDiagDir(j); - - if (IsTileType(t, MP_STREET) && - GetRoadTileType(t) == ROAD_TILE_DEPOT && - IsTileOwner(t, _current_player) && - GetRoadDepotDirection(t) == ReverseDiagDir(j)) { - p->ainew.depot_tile = t; - p->ainew.depot_direction = ReverseDiagDir(j); - p->ainew.state = AI_STATE_VERIFY_ROUTE; - return; - } - } - } - - // This routine let depot finding start in the middle, and work his way to the stations - // It makes depot placing nicer :) - i = p->ainew.path_info.route_length / 2; - g = 1; - while (i > 1 && i < p->ainew.path_info.route_length - 2) { - i += g; - g *= -1; - (g < 0?g--:g++); - - if (p->ainew.path_info.route_extra[i] != 0 || p->ainew.path_info.route_extra[i+1] != 0) { - // Bridge or tunnel.. we can't place a depot there - continue; - } - - tile = p->ainew.path_info.route[i]; - - for (j = 0; j < 4; j++) { - TileIndex t = tile + TileOffsByDiagDir(j); - - // It may not be placed on the road/rail itself - // And because it is not build yet, we can't see it on the tile.. - // So check the surrounding tiles :) - if (t == p->ainew.path_info.route[i - 1] || - t == p->ainew.path_info.route[i + 1]) { - continue; - } - // Not around a bridge? - if (p->ainew.path_info.route_extra[i] != 0) continue; - if (IsTileType(tile, MP_TUNNELBRIDGE)) continue; - // Is the terrain clear? - if (IsTileType(t, MP_CLEAR) || IsTileType(t, MP_TREES)) { - // If the current tile is on a slope then we do not allow this - if (GetTileSlope(tile, NULL) != SLOPE_FLAT) continue; - // Check if everything went okay.. - r = AiNew_Build_Depot(p, t, ReverseDiagDir(j), 0); - if (CmdFailed(r)) continue; - // Found a spot! - p->ainew.new_cost += r; - p->ainew.depot_tile = t; - p->ainew.depot_direction = ReverseDiagDir(j); // Reverse direction - p->ainew.state = AI_STATE_VERIFY_ROUTE; - return; - } - } - } - - // Failed to find a depot? - p->ainew.state = AI_STATE_NOTHING; -} - - -// This function calculates how many vehicles there are needed on this -// traject. -// It works pretty simple: get the length, see how much we move around -// and hussle that, and you know how many vehicles there are needed. -// It returns the cost for the vehicles -static int AiNew_HowManyVehicles(Player *p) -{ - if (p->ainew.tbt == AI_BUS) { - // For bus-routes we look at the time before we are back in the station - EngineID i; - int length, tiles_a_day; - int amount; - i = AiNew_PickVehicle(p); - if (i == INVALID_ENGINE) return 0; - // Passenger run.. how long is the route? - length = p->ainew.path_info.route_length; - // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! - tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; - // We want a vehicle in a station once a month at least, so, calculate it! - // (the * 2 is because we have 2 stations ;)) - amount = length * 2 * 2 / tiles_a_day / 30; - if (amount == 0) amount = 1; - return amount; - } else if (p->ainew.tbt == AI_TRUCK) { - // For truck-routes we look at the cargo - EngineID i; - int length, amount, tiles_a_day; - int max_cargo; - i = AiNew_PickVehicle(p); - if (i == INVALID_ENGINE) return 0; - // Passenger run.. how long is the route? - length = p->ainew.path_info.route_length; - // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! - tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; - if (p->ainew.from_deliver) { - max_cargo = GetIndustry(p->ainew.from_ic)->total_production[0]; - } else { - max_cargo = GetIndustry(p->ainew.to_ic)->total_production[0]; - } - - // This is because moving 60% is more than we can dream of! - max_cargo *= 0.6; - // We want all the cargo to be gone in a month.. so, we know the cargo it delivers - // we know what the vehicle takes with him, and we know the time it takes him - // to get back here.. now let's do some math! - amount = 2 * length * max_cargo / tiles_a_day / 30 / RoadVehInfo(i)->capacity; - amount += 1; - return amount; - } else { - // Currently not supported - return 0; - } -} - - -// This function checks: -// - If the route went okay -// - Calculates the amount of money needed to build the route -// - Calculates how much vehicles needed for the route -static void AiNew_State_VerifyRoute(Player *p) -{ - int res, i; - assert(p->ainew.state == AI_STATE_VERIFY_ROUTE); - - // Let's calculate the cost of the path.. - // new_cost already contains the cost of the stations - p->ainew.path_info.position = -1; - - do { - p->ainew.path_info.position++; - p->ainew.new_cost += AiNew_Build_RoutePart(p, &p->ainew.path_info, DC_QUERY_COST); - } while (p->ainew.path_info.position != -2); - - // Now we know the price of build station + path. Now check how many vehicles - // we need and what the price for that will be - res = AiNew_HowManyVehicles(p); - // If res == 0, no vehicle was found, or an other problem did occour - if (res == 0) { - p->ainew.state = AI_STATE_NOTHING; - return; - } - p->ainew.amount_veh = res; - p->ainew.cur_veh = 0; - - // Check how much it it going to cost us.. - for (i=0;iainew.new_cost += AiNew_Build_Vehicle(p, 0, DC_QUERY_COST); - } - - // Now we know how much the route is going to cost us - // Check if we have enough money for it! - if (p->ainew.new_cost > p->player_money - AI_MINIMUM_MONEY) { - // Too bad.. - DEBUG(ai, 1, "Insufficient funds to build route (%d)", p->ainew.new_cost); - p->ainew.state = AI_STATE_NOTHING; - return; - } - - // Now we can build the route, check the direction of the stations! - if (p->ainew.from_direction == AI_PATHFINDER_NO_DIRECTION) { - p->ainew.from_direction = AiNew_GetDirection(p->ainew.path_info.route[p->ainew.path_info.route_length-1], p->ainew.path_info.route[p->ainew.path_info.route_length-2]); - } - if (p->ainew.to_direction == AI_PATHFINDER_NO_DIRECTION) { - p->ainew.to_direction = AiNew_GetDirection(p->ainew.path_info.route[0], p->ainew.path_info.route[1]); - } - if (p->ainew.from_tile == AI_STATION_RANGE) - p->ainew.from_tile = p->ainew.path_info.route[p->ainew.path_info.route_length-1]; - if (p->ainew.to_tile == AI_STATION_RANGE) - p->ainew.to_tile = p->ainew.path_info.route[0]; - - p->ainew.state = AI_STATE_BUILD_STATION; - p->ainew.temp = 0; - - DEBUG(ai, 1, "The route is set and buildable, building 0x%X to 0x%X...", p->ainew.from_tile, p->ainew.to_tile); -} - - -// Build the stations -static void AiNew_State_BuildStation(Player *p) -{ - int res = 0; - assert(p->ainew.state == AI_STATE_BUILD_STATION); - if (p->ainew.temp == 0) { - if (!IsTileType(p->ainew.from_tile, MP_STATION)) - res = AiNew_Build_Station(p, p->ainew.tbt, p->ainew.from_tile, 0, 0, p->ainew.from_direction, DC_EXEC); - } else { - if (!IsTileType(p->ainew.to_tile, MP_STATION)) - res = AiNew_Build_Station(p, p->ainew.tbt, p->ainew.to_tile, 0, 0, p->ainew.to_direction, DC_EXEC); - p->ainew.state = AI_STATE_BUILD_PATH; - } - if (CmdFailed(res)) { - DEBUG(ai, 0, "[BuildStation] station could not be built (0x%X)", p->ainew.to_tile); - p->ainew.state = AI_STATE_NOTHING; - // If the first station _was_ build, destroy it - if (p->ainew.temp != 0) - AI_DoCommand(p->ainew.from_tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); - return; - } - p->ainew.temp++; -} - - -// Build the path -static void AiNew_State_BuildPath(Player *p) -{ - assert(p->ainew.state == AI_STATE_BUILD_PATH); - // p->ainew.temp is set to -1 when this function is called for the first time - if (p->ainew.temp == -1) { - DEBUG(ai, 1, "Starting to build new path"); - // Init the counter - p->ainew.counter = (4 - _opt.diff.competitor_speed) * AI_BUILDPATH_PAUSE + 1; - // Set the position to the startingplace (-1 because in a minute we do ++) - p->ainew.path_info.position = -1; - // And don't do this again - p->ainew.temp = 0; - } - // Building goes very fast on normal rate, so we are going to slow it down.. - // By let the counter count from AI_BUILDPATH_PAUSE to 0, we have a nice way :) - if (--p->ainew.counter != 0) return; - p->ainew.counter = (4 - _opt.diff.competitor_speed) * AI_BUILDPATH_PAUSE + 1; - - // Increase the building position - p->ainew.path_info.position++; - // Build route - AiNew_Build_RoutePart(p, &p->ainew.path_info, DC_EXEC); - if (p->ainew.path_info.position == -2) { - // This means we are done building! - - if (p->ainew.tbt == AI_TRUCK && !_patches.roadveh_queue) { - // If they not queue, they have to go up and down to try again at a station... - // We don't want that, so try building some road left or right of the station - int dir1, dir2, dir3; - TileIndex tile; - int i, ret; - for (i=0;i<2;i++) { - if (i == 0) { - tile = p->ainew.from_tile + TileOffsByDiagDir(p->ainew.from_direction); - dir1 = p->ainew.from_direction - 1; - if (dir1 < 0) dir1 = 3; - dir2 = p->ainew.from_direction + 1; - if (dir2 > 3) dir2 = 0; - dir3 = p->ainew.from_direction; - } else { - tile = p->ainew.to_tile + TileOffsByDiagDir(p->ainew.to_direction); - dir1 = p->ainew.to_direction - 1; - if (dir1 < 0) dir1 = 3; - dir2 = p->ainew.to_direction + 1; - if (dir2 > 3) dir2 = 0; - dir3 = p->ainew.to_direction; - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir1)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - dir1 = TileOffsByDiagDir(dir1); - if (IsTileType(tile + dir1, MP_CLEAR) || IsTileType(tile + dir1, MP_TREES)) { - ret = AI_DoCommand(tile+dir1, AiNew_GetRoadDirection(tile, tile+dir1, tile+dir1+dir1), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - if (IsTileType(tile + dir1 + dir1, MP_CLEAR) || IsTileType(tile + dir1 + dir1, MP_TREES)) - AI_DoCommand(tile+dir1+dir1, AiNew_GetRoadDirection(tile+dir1, tile+dir1+dir1, tile+dir1+dir1+dir1), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir2)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - dir2 = TileOffsByDiagDir(dir2); - if (IsTileType(tile + dir2, MP_CLEAR) || IsTileType(tile + dir2, MP_TREES)) { - ret = AI_DoCommand(tile+dir2, AiNew_GetRoadDirection(tile, tile+dir2, tile+dir2+dir2), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - if (IsTileType(tile + dir2 + dir2, MP_CLEAR) || IsTileType(tile + dir2 + dir2, MP_TREES)) - AI_DoCommand(tile+dir2+dir2, AiNew_GetRoadDirection(tile+dir2, tile+dir2+dir2, tile+dir2+dir2+dir2), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - - ret = AI_DoCommand(tile, DiagDirToRoadBits(dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - dir3 = TileOffsByDiagDir(dir3); - if (IsTileType(tile + dir3, MP_CLEAR) || IsTileType(tile + dir3, MP_TREES)) { - ret = AI_DoCommand(tile+dir3, AiNew_GetRoadDirection(tile, tile+dir3, tile+dir3+dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - if (!CmdFailed(ret)) { - if (IsTileType(tile + dir3 + dir3, MP_CLEAR) || IsTileType(tile + dir3 + dir3, MP_TREES)) - AI_DoCommand(tile+dir3+dir3, AiNew_GetRoadDirection(tile+dir3, tile+dir3+dir3, tile+dir3+dir3+dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); - } - } - } - } - } - - DEBUG(ai, 1, "Finished building path, cost: %d", p->ainew.new_cost); - p->ainew.state = AI_STATE_BUILD_DEPOT; - } -} - - -// Builds the depot -static void AiNew_State_BuildDepot(Player *p) -{ - int res = 0; - assert(p->ainew.state == AI_STATE_BUILD_DEPOT); - - if (IsTileType(p->ainew.depot_tile, MP_STREET) && GetRoadTileType(p->ainew.depot_tile) == ROAD_TILE_DEPOT) { - if (IsTileOwner(p->ainew.depot_tile, _current_player)) { - // The depot is already built - p->ainew.state = AI_STATE_BUILD_VEHICLE; - return; - } else { - // There is a depot, but not of our team! :( - p->ainew.state = AI_STATE_NOTHING; - return; - } - } - - // There is a bus on the tile we want to build road on... idle till he is gone! (BAD PERSON! :p) - if (!EnsureNoVehicle(p->ainew.depot_tile + TileOffsByDiagDir(p->ainew.depot_direction))) - return; - - res = AiNew_Build_Depot(p, p->ainew.depot_tile, p->ainew.depot_direction, DC_EXEC); - if (CmdFailed(res)) { - DEBUG(ai, 0, "[BuildDepot] depot could not be built (0x%X)", p->ainew.depot_tile); - p->ainew.state = AI_STATE_NOTHING; - return; - } - - p->ainew.state = AI_STATE_BUILD_VEHICLE; - p->ainew.idle = 10; - p->ainew.veh_main_id = INVALID_VEHICLE; -} - - -// Build vehicles -static void AiNew_State_BuildVehicle(Player *p) -{ - int res; - assert(p->ainew.state == AI_STATE_BUILD_VEHICLE); - - // Check if we need to build a vehicle - if (p->ainew.amount_veh == 0) { - // Nope, we are done! - // This means: we are all done! The route is open.. go back to NOTHING - // He will idle some time and it will all start over again.. :) - p->ainew.state = AI_STATE_ACTION_DONE; - return; - } - if (--p->ainew.idle != 0) return; - // It is realistic that the AI can only build 1 vehicle a day.. - // This makes sure of that! - p->ainew.idle = AI_BUILD_VEHICLE_TIME_BETWEEN; - - // Build the vehicle - res = AiNew_Build_Vehicle(p, p->ainew.depot_tile, DC_EXEC); - if (CmdFailed(res)) { - // This happens when the AI can't build any more vehicles! - p->ainew.state = AI_STATE_NOTHING; - return; - } - // Increase the current counter - p->ainew.cur_veh++; - // Decrease the total counter - p->ainew.amount_veh--; - // Go give some orders! - p->ainew.state = AI_STATE_WAIT_FOR_BUILD; -} - - -// Put the stations in the order list -static void AiNew_State_GiveOrders(Player *p) -{ - int idx; - Order order; - - assert(p->ainew.state == AI_STATE_GIVE_ORDERS); - - if (p->ainew.veh_main_id != INVALID_VEHICLE) { - AI_DoCommand(0, p->ainew.veh_id + (p->ainew.veh_main_id << 16), 0, DC_EXEC, CMD_CLONE_ORDER); - - p->ainew.state = AI_STATE_START_VEHICLE; - return; - } else { - p->ainew.veh_main_id = p->ainew.veh_id; - } - - // Very handy for AI, goto depot.. but yeah, it needs to be activated ;) - if (_patches.gotodepot) { - idx = 0; - order.type = OT_GOTO_DEPOT; - order.flags = OF_UNLOAD; - order.dest = GetDepotByTile(p->ainew.depot_tile)->index; - AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - } - - idx = 0; - order.type = OT_GOTO_STATION; - order.flags = 0; - order.dest = GetStationIndex(p->ainew.to_tile); - if (p->ainew.tbt == AI_TRUCK && p->ainew.to_deliver) - order.flags |= OF_FULL_LOAD; - AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - - idx = 0; - order.type = OT_GOTO_STATION; - order.flags = 0; - order.dest = GetStationIndex(p->ainew.from_tile); - if (p->ainew.tbt == AI_TRUCK && p->ainew.from_deliver) - order.flags |= OF_FULL_LOAD; - AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); - - // Start the engines! - p->ainew.state = AI_STATE_START_VEHICLE; -} - - -// Start the vehicle -static void AiNew_State_StartVehicle(Player *p) -{ - assert(p->ainew.state == AI_STATE_START_VEHICLE); - - // Skip the first order if it is a second vehicle - // This to make vehicles go different ways.. - if (p->ainew.cur_veh & 1) - AI_DoCommand(0, p->ainew.veh_id, 0, DC_EXEC, CMD_SKIP_ORDER); - - // 3, 2, 1... go! (give START_STOP command ;)) - AI_DoCommand(0, p->ainew.veh_id, 0, DC_EXEC, CMD_START_STOP_ROADVEH); - // Try to build an other vehicle (that function will stop building when needed) - p->ainew.idle = 10; - p->ainew.state = AI_STATE_BUILD_VEHICLE; -} - - -// Repays money -static void AiNew_State_RepayMoney(Player *p) -{ - uint i; - - for (i = 0; i < AI_LOAN_REPAY; i++) { - AI_DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); - } - p->ainew.state = AI_STATE_ACTION_DONE; -} - - -static void AiNew_CheckVehicle(Player *p, Vehicle *v) -{ - // When a vehicle is under the 6 months, we don't check for anything - if (v->age < 180) return; - - // When a vehicle is older then 1 year, it should make money... - if (v->age > 360) { - // If both years together are not more than AI_MINIMUM_ROUTE_PROFIT, - // it is not worth the line I guess... - if (v->profit_last_year + v->profit_this_year < AI_MINIMUM_ROUTE_PROFIT || - (v->reliability * 100 >> 16) < 40) { - // There is a possibility that the route is fucked up... - if (v->cargo_days > AI_VEHICLE_LOST_DAYS) { - // The vehicle is lost.. check the route, or else, get the vehicle - // back to a depot - // TODO: make this piece of code - } - - - // We are already sending him back - if (AiNew_GetSpecialVehicleFlag(p, v) & AI_VEHICLEFLAG_SELL) { - if (v->type == VEH_Road && IsTileDepotType(v->tile, TRANSPORT_ROAD) && - (v->vehstatus&VS_STOPPED)) { - // We are at the depot, sell the vehicle - AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); - } - return; - } - - if (!AiNew_SetSpecialVehicleFlag(p, v, AI_VEHICLEFLAG_SELL)) return; - { - int ret = 0; - if (v->type == VEH_Road) - ret = AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); - // This means we can not find a depot :s - // if (CmdFailed(ret)) - } - } - } -} - - -// Checks all vehicles if they are still valid and make money and stuff -static void AiNew_State_CheckAllVehicles(Player *p) -{ - Vehicle *v; - - FOR_ALL_VEHICLES(v) { - if (v->owner != p->index) continue; - // Currently, we only know how to handle road-vehicles - if (v->type != VEH_Road) continue; - - AiNew_CheckVehicle(p, v); - } - - p->ainew.state = AI_STATE_ACTION_DONE; -} - - -// Using the technique simular to the original AI -// Keeps things logical -// It really should be in the same order as the AI_STATE's are! -static AiNew_StateFunction* const _ainew_state[] = { - NULL, - AiNew_State_FirstTime, - AiNew_State_Nothing, - AiNew_State_WakeUp, - AiNew_State_LocateRoute, - AiNew_State_FindStation, - AiNew_State_FindPath, - AiNew_State_FindDepot, - AiNew_State_VerifyRoute, - AiNew_State_BuildStation, - AiNew_State_BuildPath, - AiNew_State_BuildDepot, - AiNew_State_BuildVehicle, - NULL, - AiNew_State_GiveOrders, - AiNew_State_StartVehicle, - AiNew_State_RepayMoney, - AiNew_State_CheckAllVehicles, - AiNew_State_ActionDone, - NULL, -}; - -static void AiNew_OnTick(Player *p) -{ - if (_ainew_state[p->ainew.state] != NULL) - _ainew_state[p->ainew.state](p); -} - - -void AiNewDoGameLoop(Player *p) -{ - if (p->ainew.state == AI_STATE_STARTUP) { - // The AI just got alive! - p->ainew.state = AI_STATE_FIRST_TIME; - p->ainew.tick = 0; - - // Only startup the AI - return; - } - - // We keep a ticker. We use it for competitor_speed - p->ainew.tick++; - - // If we come here, we can do a tick.. do so! - AiNew_OnTick(p); -} diff --git a/src/ai/trolly/trolly.cpp b/src/ai/trolly/trolly.cpp new file mode 100644 --- /dev/null +++ b/src/ai/trolly/trolly.cpp @@ -0,0 +1,1353 @@ +/* $Id$ */ + +/* + * This AI was created as a direct reaction to the big demand for some good AIs + * in OTTD. Too bad it never left alpha-stage, and it is considered dead in its + * current form. + * By the time of writing this, we, the creator of this AI and a good friend of + * mine, are designing a whole new AI-system that allows us to create AIs + * easier and without all the fuzz we encountered while I was working on this + * AI. By the time that system is finished, you can expect that this AI will + * dissapear, because it is pretty obselete and bad programmed. + * + * Meanwhile I wish you all much fun with this AI; if you are interested as + * AI-developer in this AI, I advise you not stare too long to some code, some + * things in here really are... strange ;) But in either way: enjoy :) + * + * -- TrueLight :: 2005-09-01 + */ + +#include "../../stdafx.h" +#include "../../openttd.h" +#include "../../debug.h" +#include "../../functions.h" +#include "../../road_map.h" +#include "../../station_map.h" +#include "table/strings.h" +#include "../../map.h" +#include "../../tile.h" +#include "../../command.h" +#include "trolly.h" +#include "../../town.h" +#include "../../industry.h" +#include "../../station.h" +#include "../../engine.h" +#include "../../gui.h" +#include "../../depot.h" +#include "../../vehicle.h" +#include "../../date.h" +#include "../ai.h" + +// This function is called after StartUp. It is the init of an AI +static void AiNew_State_FirstTime(Player *p) +{ + // This assert is used to protect those function from misuse + // You have quickly a small mistake in the state-array + // With that, everything would go wrong. Finding that, is almost impossible + // With this assert, that problem can never happen. + assert(p->ainew.state == AI_STATE_FIRST_TIME); + // We first have to init some things + + if (_current_player == 1) ShowErrorMessage(INVALID_STRING_ID, TEMP_AI_IN_PROGRESS, 0, 0); + + // The PathFinder (AyStar) + // TODO: Maybe when an AI goes bankrupt, this is de-init + // or when coming from a savegame.. should be checked out! + p->ainew.path_info.start_tile_tl = 0; + p->ainew.path_info.start_tile_br = 0; + p->ainew.path_info.end_tile_tl = 0; + p->ainew.path_info.end_tile_br = 0; + p->ainew.pathfinder = new_AyStar_AiPathFinder(12, &p->ainew.path_info); + + p->ainew.idle = 0; + p->ainew.last_vehiclecheck_date = _date; + + // We ALWAYS start with a bus route.. just some basic money ;) + p->ainew.action = AI_ACTION_BUS_ROUTE; + + // Let's popup the news, and after that, start building.. + p->ainew.state = AI_STATE_WAKE_UP; +} + + +// This function just waste some time +// It keeps it more real. The AI can build on such tempo no normal user +// can ever keep up with that. The competitor_speed already delays a bit +// but after the AI finished a track it really needs to go to sleep. +// +// Let's say, we sleep between one and three days if the AI is put on Very Fast. +// This means that on Very Slow it will be between 16 and 48 days.. slow enough? +static void AiNew_State_Nothing(Player *p) +{ + assert(p->ainew.state == AI_STATE_NOTHING); + // If we are done idling, start over again + if (p->ainew.idle == 0) p->ainew.idle = AI_RandomRange(DAY_TICKS * 2) + DAY_TICKS; + if (--p->ainew.idle == 0) { + // We are done idling.. what you say? Let's do something! + // I mean.. the next tick ;) + p->ainew.state = AI_STATE_WAKE_UP; + } +} + + +// This function picks out a task we are going to do. +// Currently supported: +// - Make new route +// - Check route +// - Build HQ +static void AiNew_State_WakeUp(Player *p) +{ + int32 money; + int c; + assert(p->ainew.state == AI_STATE_WAKE_UP); + // First, check if we have a HQ + if (p->location_of_house == 0) { + // We have no HQ yet, build one on a random place + // Random till we found a place for it! + // TODO: this should not be on a random place.. + AiNew_Build_CompanyHQ(p, AI_Random() % MapSize()); + // Enough for now, but we want to come back here the next time + // so we do not change any status + return; + } + + money = p->player_money - AI_MINIMUM_MONEY; + + // Let's pick an action! + if (p->ainew.action == AI_ACTION_NONE) { + c = AI_Random() & 0xFF; + if (p->current_loan > 0 && + p->old_economy[1].income > AI_MINIMUM_INCOME_FOR_LOAN && + c < 10) { + p->ainew.action = AI_ACTION_REPAY_LOAN; + } else if (p->ainew.last_vehiclecheck_date + AI_DAYS_BETWEEN_VEHICLE_CHECKS < _date) { + // Check all vehicles once in a while + p->ainew.action = AI_ACTION_CHECK_ALL_VEHICLES; + p->ainew.last_vehiclecheck_date = _date; + } else if (c < 100 && !_patches.ai_disable_veh_roadveh) { + // Do we have any spots for road-vehicles left open? + if (GetFreeUnitNumber(VEH_Road) <= _patches.max_roadveh) { + if (c < 85) { + p->ainew.action = AI_ACTION_TRUCK_ROUTE; + } else { + p->ainew.action = AI_ACTION_BUS_ROUTE; + } + } +#if 0 + } else if (c < 200 && !_patches.ai_disable_veh_train) { + if (GetFreeUnitNumber(VEH_Train) <= _patches.max_trains) { + p->ainew.action = AI_ACTION_TRAIN_ROUTE; + } +#endif + } + + p->ainew.counter = 0; + } + + if (p->ainew.counter++ > AI_MAX_TRIES_FOR_SAME_ROUTE) { + p->ainew.action = AI_ACTION_NONE; + return; + } + + if (_patches.ai_disable_veh_roadveh && ( + p->ainew.action == AI_ACTION_BUS_ROUTE || + p->ainew.action == AI_ACTION_TRUCK_ROUTE + )) { + p->ainew.action = AI_ACTION_NONE; + return; + } + + if (p->ainew.action == AI_ACTION_REPAY_LOAN && + money > AI_MINIMUM_LOAN_REPAY_MONEY) { + // We start repaying some money.. + p->ainew.state = AI_STATE_REPAY_MONEY; + return; + } + + if (p->ainew.action == AI_ACTION_CHECK_ALL_VEHICLES) { + p->ainew.state = AI_STATE_CHECK_ALL_VEHICLES; + return; + } + + // It is useless to start finding a route if we don't have enough money + // to build the route anyway.. + if (p->ainew.action == AI_ACTION_BUS_ROUTE && + money > AI_MINIMUM_BUS_ROUTE_MONEY) { + if (GetFreeUnitNumber(VEH_Road) > _patches.max_roadveh) { + p->ainew.action = AI_ACTION_NONE; + return; + } + p->ainew.cargo = AI_NEED_CARGO; + p->ainew.state = AI_STATE_LOCATE_ROUTE; + p->ainew.tbt = AI_BUS; // Bus-route + return; + } + if (p->ainew.action == AI_ACTION_TRUCK_ROUTE && + money > AI_MINIMUM_TRUCK_ROUTE_MONEY) { + if (GetFreeUnitNumber(VEH_Road) > _patches.max_roadveh) { + p->ainew.action = AI_ACTION_NONE; + return; + } + p->ainew.cargo = AI_NEED_CARGO; + p->ainew.last_id = 0; + p->ainew.state = AI_STATE_LOCATE_ROUTE; + p->ainew.tbt = AI_TRUCK; + return; + } + + p->ainew.state = AI_STATE_NOTHING; +} + + +static void AiNew_State_ActionDone(Player *p) +{ + p->ainew.action = AI_ACTION_NONE; + p->ainew.state = AI_STATE_NOTHING; +} + + +// Check if a city or industry is good enough to start a route there +static bool AiNew_Check_City_or_Industry(Player *p, int ic, byte type) +{ + if (type == AI_CITY) { + const Town* t = GetTown(ic); + const Station* st; + uint count = 0; + int j = 0; + + // We don't like roadconstructions, don't even true such a city + if (t->road_build_months != 0) return false; + + // Check if the rating in a city is high enough + // If not, take a chance if we want to continue + if (t->ratings[_current_player] < 0 && AI_CHANCE16(1,4)) return false; + + if (t->max_pass - t->act_pass < AI_CHECKCITY_NEEDED_CARGO && !AI_CHANCE16(1,AI_CHECKCITY_CITY_CHANCE)) return false; + + // Check if we have build a station in this town the last 6 months + // else we don't do it. This is done, because stat updates can be slow + // and sometimes it takes up to 4 months before the stats are corectly. + // This way we don't get 12 busstations in one city of 100 population ;) + FOR_ALL_STATIONS(st) { + // Do we own it? + if (st->owner == _current_player) { + // Are we talking busses? + if (p->ainew.tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) != FACIL_BUS_STOP) continue; + // Is it the same city as we are in now? + if (st->town != t) continue; + // When was this station build? + if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; + // Cound the amount of stations in this city that we own + count++; + } else { + // We do not own it, request some info about the station + // we want to know if this station gets the same good. If so, + // we want to know its rating. If it is too high, we are not going + // to build there + if (!st->goods[CT_PASSENGERS].last_speed) continue; + // Is it around our city + if (DistanceManhattan(st->xy, t->xy) > 10) continue; + // It does take this cargo.. what is his rating? + if (st->goods[CT_PASSENGERS].rating < AI_CHECKCITY_CARGO_RATING) continue; + j++; + // When this is the first station, we build a second with no problem ;) + if (j == 1) continue; + // The rating is high.. second station... + // a little chance that we still continue + // But if there are 3 stations of this size, we never go on... + if (j == 2 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; + // We don't like this station :( + return false; + } + } + + // We are about to add one... + count++; + // Check if we the city can provide enough cargo for this amount of stations.. + if (count * AI_CHECKCITY_CARGO_PER_STATION > t->max_pass) return false; + + // All check are okay, so we can build here! + return true; + } + if (type == AI_INDUSTRY) { + const Industry* i = GetIndustry(ic); + const Station* st; + int count = 0; + int j = 0; + + if (i->town != NULL && i->town->ratings[_current_player] < 0 && AI_CHANCE16(1,4)) return false; + + // No limits on delevering stations! + // Or for industry that does not give anything yet + if (i->produced_cargo[0] == CT_INVALID || i->total_production[0] == 0) return true; + + if (i->total_production[0] - i->total_transported[0] < AI_CHECKCITY_NEEDED_CARGO) return false; + + // Check if we have build a station in this town the last 6 months + // else we don't do it. This is done, because stat updates can be slow + // and sometimes it takes up to 4 months before the stats are corectly. + FOR_ALL_STATIONS(st) { + // Do we own it? + if (st->owner == _current_player) { + // Are we talking trucks? + if (p->ainew.tbt == AI_TRUCK && (FACIL_TRUCK_STOP & st->facilities) != FACIL_TRUCK_STOP) continue; + // Is it the same city as we are in now? + if (st->town != i->town) continue; + // When was this station build? + if (_date - st->build_date < AI_CHECKCITY_DATE_BETWEEN) return false; + // Cound the amount of stations in this city that we own + count++; + } else { + // We do not own it, request some info about the station + // we want to know if this station gets the same good. If so, + // we want to know its rating. If it is too high, we are not going + // to build there + if (i->produced_cargo[0] == CT_INVALID) continue; + // It does not take this cargo + if (!st->goods[i->produced_cargo[0]].last_speed) continue; + // Is it around our industry + if (DistanceManhattan(st->xy, i->xy) > 5) continue; + // It does take this cargo.. what is his rating? + if (st->goods[i->produced_cargo[0]].rating < AI_CHECKCITY_CARGO_RATING) continue; + j++; + // The rating is high.. a little chance that we still continue + // But if there are 2 stations of this size, we never go on... + if (j == 1 && AI_CHANCE16(1, AI_CHECKCITY_CARGO_RATING_CHANCE)) continue; + // We don't like this station :( + return false; + } + } + + // We are about to add one... + count++; + // Check if we the city can provide enough cargo for this amount of stations.. + if (count * AI_CHECKCITY_CARGO_PER_STATION > i->total_production[0]) return false; + + // All check are okay, so we can build here! + return true; + } + + return true; +} + + +// This functions tries to locate a good route +static void AiNew_State_LocateRoute(Player *p) +{ + assert(p->ainew.state == AI_STATE_LOCATE_ROUTE); + // For now, we only support PASSENGERS, CITY and BUSSES + + // We don't have a route yet + if (p->ainew.cargo == AI_NEED_CARGO) { + p->ainew.new_cost = 0; // No cost yet + p->ainew.temp = -1; + // Reset the counter + p->ainew.counter = 0; + + p->ainew.from_ic = -1; + p->ainew.to_ic = -1; + if (p->ainew.tbt == AI_BUS) { + // For now we only have a passenger route + p->ainew.cargo = CT_PASSENGERS; + + // Find a route to cities + p->ainew.from_type = AI_CITY; + p->ainew.to_type = AI_CITY; + } else if (p->ainew.tbt == AI_TRUCK) { + p->ainew.cargo = AI_NO_CARGO; + + p->ainew.from_type = AI_INDUSTRY; + p->ainew.to_type = AI_INDUSTRY; + } + + // Now we are doing initing, we wait one tick + return; + } + + // Increase the counter and abort if it is taking too long! + p->ainew.counter++; + if (p->ainew.counter > AI_LOCATE_ROUTE_MAX_COUNTER) { + // Switch back to doing nothing! + p->ainew.state = AI_STATE_NOTHING; + return; + } + + // We are going to locate a city from where we are going to connect + if (p->ainew.from_ic == -1) { + if (p->ainew.temp == -1) { + // First, we pick a random spot to search from + if (p->ainew.from_type == AI_CITY) { + p->ainew.temp = AI_RandomRange(GetMaxTownIndex() + 1); + } else { + p->ainew.temp = AI_RandomRange(GetMaxIndustryIndex() + 1); + } + } + + if (!AiNew_Check_City_or_Industry(p, p->ainew.temp, p->ainew.from_type)) { + // It was not a valid city + // increase the temp with one, and return. We will come back later here + // to try again + p->ainew.temp++; + if (p->ainew.from_type == AI_CITY) { + if (p->ainew.temp > GetMaxTownIndex()) p->ainew.temp = 0; + } else { + if (p->ainew.temp > GetMaxIndustryIndex()) p->ainew.temp = 0; + } + + // Don't do an attempt if we are trying the same id as the last time... + if (p->ainew.last_id == p->ainew.temp) return; + p->ainew.last_id = p->ainew.temp; + + return; + } + + // We found a good city/industry, save the data of it + p->ainew.from_ic = p->ainew.temp; + + // Start the next tick with finding a to-city + p->ainew.temp = -1; + return; + } + + // Find a to-city + if (p->ainew.temp == -1) { + // First, we pick a random spot to search to + if (p->ainew.to_type == AI_CITY) { + p->ainew.temp = AI_RandomRange(GetMaxTownIndex() + 1); + } else { + p->ainew.temp = AI_RandomRange(GetMaxIndustryIndex() + 1); + } + } + + // The same city is not allowed + // Also check if the city is valid + if (p->ainew.temp != p->ainew.from_ic && AiNew_Check_City_or_Industry(p, p->ainew.temp, p->ainew.to_type)) { + // Maybe it is valid.. + + /* We need to know if they are not to far apart from eachother.. + * We do that by checking how much cargo we have to move and how long the + * route is. + */ + + if (p->ainew.from_type == AI_CITY && p->ainew.tbt == AI_BUS) { + const Town* town_from = GetTown(p->ainew.from_ic); + const Town* town_temp = GetTown(p->ainew.temp); + uint distance = DistanceManhattan(town_from->xy, town_temp->xy); + int max_cargo; + + max_cargo = town_from->max_pass + town_temp->max_pass; + max_cargo -= town_from->act_pass + town_temp->act_pass; + + // max_cargo is now the amount of cargo we can move between the two cities + // If it is more than the distance, we allow it + if (distance <= max_cargo * AI_LOCATEROUTE_BUS_CARGO_DISTANCE) { + // We found a good city/industry, save the data of it + p->ainew.to_ic = p->ainew.temp; + p->ainew.state = AI_STATE_FIND_STATION; + + DEBUG(ai, 1, "[LocateRoute] found bus-route of %d tiles long (from %d to %d)", + distance, + p->ainew.from_ic, + p->ainew.temp + ); + + p->ainew.from_tile = 0; + p->ainew.to_tile = 0; + + return; + } + } else if (p->ainew.tbt == AI_TRUCK) { + const Industry* ind_from = GetIndustry(p->ainew.from_ic); + const Industry* ind_temp = GetIndustry(p->ainew.temp); + bool found = false; + int max_cargo = 0; + uint i; + + // TODO: in max_cargo, also check other cargo (beside [0]) + // First we check if the from_ic produces cargo that this ic accepts + if (ind_from->produced_cargo[0] != CT_INVALID && ind_from->total_production[0] != 0) { + for (i = 0; i < lengthof(ind_temp->accepts_cargo); i++) { + if (ind_temp->accepts_cargo[i] == CT_INVALID) break; + if (ind_from->produced_cargo[0] == ind_temp->accepts_cargo[i]) { + // Found a compatible industry + max_cargo = ind_from->total_production[0] - ind_from->total_transported[0]; + found = true; + p->ainew.from_deliver = true; + p->ainew.to_deliver = false; + break; + } + } + } + if (!found && ind_temp->produced_cargo[0] != CT_INVALID && ind_temp->total_production[0] != 0) { + // If not check if the current ic produces cargo that the from_ic accepts + for (i = 0; i < lengthof(ind_from->accepts_cargo); i++) { + if (ind_from->accepts_cargo[i] == CT_INVALID) break; + if (ind_temp->produced_cargo[0] == ind_from->accepts_cargo[i]) { + // Found a compatbiel industry + found = true; + max_cargo = ind_temp->total_production[0] - ind_temp->total_transported[0]; + p->ainew.from_deliver = false; + p->ainew.to_deliver = true; + break; + } + } + } + if (found) { + // Yeah, they are compatible!!! + // Check the length against the amount of goods + uint distance = DistanceManhattan(ind_from->xy, ind_temp->xy); + + if (distance > AI_LOCATEROUTE_TRUCK_MIN_DISTANCE && + distance <= max_cargo * AI_LOCATEROUTE_TRUCK_CARGO_DISTANCE) { + p->ainew.to_ic = p->ainew.temp; + if (p->ainew.from_deliver) { + p->ainew.cargo = ind_from->produced_cargo[0]; + } else { + p->ainew.cargo = ind_temp->produced_cargo[0]; + } + p->ainew.state = AI_STATE_FIND_STATION; + + DEBUG(ai, 1, "[LocateRoute] found truck-route of %d tiles long (from %d to %d)", + distance, + p->ainew.from_ic, + p->ainew.temp + ); + + p->ainew.from_tile = 0; + p->ainew.to_tile = 0; + + return; + } + } + } + } + + // It was not a valid city + // increase the temp with one, and return. We will come back later here + // to try again + p->ainew.temp++; + if (p->ainew.to_type == AI_CITY) { + if (p->ainew.temp > GetMaxTownIndex()) p->ainew.temp = 0; + } else { + if (p->ainew.temp > GetMaxIndustryIndex()) p->ainew.temp = 0; + } + + // Don't do an attempt if we are trying the same id as the last time... + if (p->ainew.last_id == p->ainew.temp) return; + p->ainew.last_id = p->ainew.temp; +} + + +// Check if there are not more than a certain amount of vehicles pointed to a certain +// station. This to prevent 10 busses going to one station, which gives... problems ;) +static bool AiNew_CheckVehicleStation(Player *p, Station *st) +{ + int count = 0; + Vehicle *v; + + // Also check if we don't have already a lot of busses to this city... + FOR_ALL_VEHICLES(v) { + if (v->owner == _current_player) { + const Order *order; + + FOR_VEHICLE_ORDERS(v, order) { + if (order->type == OT_GOTO_STATION && GetStation(order->dest) == st) { + // This vehicle has this city in its list + count++; + } + } + } + } + + if (count > AI_CHECK_MAX_VEHICLE_PER_STATION) return false; + return true; +} + +// This function finds a good spot for a station +static void AiNew_State_FindStation(Player *p) +{ + TileIndex tile; + Station *st; + int count = 0; + EngineID i; + TileIndex new_tile = 0; + byte direction = 0; + Town *town = NULL; + assert(p->ainew.state == AI_STATE_FIND_STATION); + + if (p->ainew.from_tile == 0) { + // First we scan for a station in the from-city + if (p->ainew.from_type == AI_CITY) { + town = GetTown(p->ainew.from_ic); + tile = town->xy; + } else { + tile = GetIndustry(p->ainew.from_ic)->xy; + } + } else if (p->ainew.to_tile == 0) { + // Second we scan for a station in the to-city + if (p->ainew.to_type == AI_CITY) { + town = GetTown(p->ainew.to_ic); + tile = town->xy; + } else { + tile = GetIndustry(p->ainew.to_ic)->xy; + } + } else { + // Unsupported request + // Go to FIND_PATH + p->ainew.temp = -1; + p->ainew.state = AI_STATE_FIND_PATH; + return; + } + + // First, we are going to look at the stations that already exist inside the city + // If there is enough cargo left in the station, we take that station + // If that is not possible, and there are more than 2 stations in the city, abort + i = AiNew_PickVehicle(p); + // Euhmz, this should not happen _EVER_ + // Quit finding a route... + if (i == INVALID_ENGINE) { + p->ainew.state = AI_STATE_NOTHING; + return; + } + + FOR_ALL_STATIONS(st) { + if (st->owner == _current_player) { + if (p->ainew.tbt == AI_BUS && (FACIL_BUS_STOP & st->facilities) == FACIL_BUS_STOP) { + if (st->town == town) { + // Check how much cargo there is left in the station + if ((st->goods[p->ainew.cargo].waiting_acceptance & 0xFFF) > RoadVehInfo(i)->capacity * AI_STATION_REUSE_MULTIPLER) { + if (AiNew_CheckVehicleStation(p, st)) { + // We did found a station that was good enough! + new_tile = st->xy; + direction = GetRoadStopDir(st->xy); + break; + } + } + count++; + } + } + } + } + // We are going to add a new station... + if (new_tile == 0) count++; + // No more than 2 stations allowed in a city + // This is because only the best 2 stations of one cargo do get any cargo + if (count > 2) { + p->ainew.state = AI_STATE_NOTHING; + return; + } + + if (new_tile == 0 && p->ainew.tbt == AI_BUS) { + uint x, y, i = 0; + int r; + uint best; + uint accepts[NUM_CARGO]; + TileIndex found_spot[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE*4]; + uint found_best[AI_FINDSTATION_TILE_RANGE*AI_FINDSTATION_TILE_RANGE*4]; + // To find a good spot we scan a range from the center, a get the point + // where we get the most cargo and where it is buildable. + // TODO: also check for station of myself and make sure we are not + // taking eachothers passangers away (bad result when it does not) + for (x = TileX(tile) - AI_FINDSTATION_TILE_RANGE; x <= TileX(tile) + AI_FINDSTATION_TILE_RANGE; x++) { + for (y = TileY(tile) - AI_FINDSTATION_TILE_RANGE; y <= TileY(tile) + AI_FINDSTATION_TILE_RANGE; y++) { + new_tile = TileXY(x, y); + if (IsTileType(new_tile, MP_CLEAR) || IsTileType(new_tile, MP_TREES)) { + // This tile we can build on! + // Check acceptance + // XXX - Get the catchment area + GetAcceptanceAroundTiles(accepts, new_tile, 1, 1, 4); + // >> 3 == 0 means no cargo + if (accepts[p->ainew.cargo] >> 3 == 0) continue; + // See if we can build the station + r = AiNew_Build_Station(p, p->ainew.tbt, new_tile, 0, 0, 0, DC_QUERY_COST); + if (CmdFailed(r)) continue; + // We can build it, so add it to found_spot + found_spot[i] = new_tile; + found_best[i++] = accepts[p->ainew.cargo]; + } + } + } + + // If i is still zero, we did not find anything + if (i == 0) { + p->ainew.state = AI_STATE_NOTHING; + return; + } + + // Go through all the found_best and check which has the highest value + best = 0; + new_tile = 0; + + for (x = 0; x < i; x++) { + if (found_best[x] > best || + (found_best[x] == best && DistanceManhattan(tile, new_tile) > DistanceManhattan(tile, found_spot[x]))) { + new_tile = found_spot[x]; + best = found_best[x]; + } + } + + // See how much it is going to cost us... + r = AiNew_Build_Station(p, p->ainew.tbt, new_tile, 0, 0, 0, DC_QUERY_COST); + p->ainew.new_cost += r; + + direction = AI_PATHFINDER_NO_DIRECTION; + } else if (new_tile == 0 && p->ainew.tbt == AI_TRUCK) { + // Truck station locater works differently.. a station can be on any place + // as long as it is in range. So we give back code AI_STATION_RANGE + // so the pathfinder routine can work it out! + new_tile = AI_STATION_RANGE; + direction = AI_PATHFINDER_NO_DIRECTION; + } + + if (p->ainew.from_tile == 0) { + p->ainew.from_tile = new_tile; + p->ainew.from_direction = direction; + // Now we found thisone, go in for to_tile + return; + } else if (p->ainew.to_tile == 0) { + p->ainew.to_tile = new_tile; + p->ainew.to_direction = direction; + // K, done placing stations! + p->ainew.temp = -1; + p->ainew.state = AI_STATE_FIND_PATH; + return; + } +} + + +// We try to find a path between 2 points +static void AiNew_State_FindPath(Player *p) +{ + int r; + assert(p->ainew.state == AI_STATE_FIND_PATH); + + // First time, init some data + if (p->ainew.temp == -1) { + // Init path_info + if (p->ainew.from_tile == AI_STATION_RANGE) { + const Industry* i = GetIndustry(p->ainew.from_ic); + + // For truck routes we take a range around the industry + p->ainew.path_info.start_tile_tl = i->xy - TileDiffXY(1, 1); + p->ainew.path_info.start_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); + p->ainew.path_info.start_direction = p->ainew.from_direction; + } else { + p->ainew.path_info.start_tile_tl = p->ainew.from_tile; + p->ainew.path_info.start_tile_br = p->ainew.from_tile; + p->ainew.path_info.start_direction = p->ainew.from_direction; + } + + if (p->ainew.to_tile == AI_STATION_RANGE) { + const Industry* i = GetIndustry(p->ainew.to_ic); + + p->ainew.path_info.end_tile_tl = i->xy - TileDiffXY(1, 1); + p->ainew.path_info.end_tile_br = i->xy + TileDiffXY(i->width + 1, i->height + 1); + p->ainew.path_info.end_direction = p->ainew.to_direction; + } else { + p->ainew.path_info.end_tile_tl = p->ainew.to_tile; + p->ainew.path_info.end_tile_br = p->ainew.to_tile; + p->ainew.path_info.end_direction = p->ainew.to_direction; + } + + p->ainew.path_info.rail_or_road = (p->ainew.tbt == AI_TRAIN); + + // First, clean the pathfinder with our new begin and endpoints + clean_AyStar_AiPathFinder(p->ainew.pathfinder, &p->ainew.path_info); + + p->ainew.temp = 0; + } + + // Start the pathfinder + r = p->ainew.pathfinder->main(p->ainew.pathfinder); + switch (r) { + case AYSTAR_NO_PATH: + DEBUG(ai, 1, "No route found by pathfinder"); + // Start all over again + p->ainew.state = AI_STATE_NOTHING; + break; + + case AYSTAR_FOUND_END_NODE: // We found the end-point + p->ainew.temp = -1; + p->ainew.state = AI_STATE_FIND_DEPOT; + break; + + // In any other case, we are still busy finding the route + default: break; + } +} + + +// This function tries to locate a good place for a depot! +static void AiNew_State_FindDepot(Player *p) +{ + // To place the depot, we walk through the route, and if we find a lovely spot (MP_CLEAR, MP_TREES), we place it there.. + // Simple, easy, works! + // To make the depot stand in the middle of the route, we start from the center.. + // But first we walk through the route see if we can find a depot that is ours + // this keeps things nice ;) + int g, i, r; + DiagDirection j; + TileIndex tile; + assert(p->ainew.state == AI_STATE_FIND_DEPOT); + + p->ainew.depot_tile = 0; + + for (i=2;iainew.path_info.route_length-2;i++) { + tile = p->ainew.path_info.route[i]; + for (j = 0; j < 4; j++) { + TileIndex t = tile + TileOffsByDiagDir(j); + + if (IsTileType(t, MP_STREET) && + GetRoadTileType(t) == ROAD_TILE_DEPOT && + IsTileOwner(t, _current_player) && + GetRoadDepotDirection(t) == ReverseDiagDir(j)) { + p->ainew.depot_tile = t; + p->ainew.depot_direction = ReverseDiagDir(j); + p->ainew.state = AI_STATE_VERIFY_ROUTE; + return; + } + } + } + + // This routine let depot finding start in the middle, and work his way to the stations + // It makes depot placing nicer :) + i = p->ainew.path_info.route_length / 2; + g = 1; + while (i > 1 && i < p->ainew.path_info.route_length - 2) { + i += g; + g *= -1; + (g < 0?g--:g++); + + if (p->ainew.path_info.route_extra[i] != 0 || p->ainew.path_info.route_extra[i+1] != 0) { + // Bridge or tunnel.. we can't place a depot there + continue; + } + + tile = p->ainew.path_info.route[i]; + + for (j = 0; j < 4; j++) { + TileIndex t = tile + TileOffsByDiagDir(j); + + // It may not be placed on the road/rail itself + // And because it is not build yet, we can't see it on the tile.. + // So check the surrounding tiles :) + if (t == p->ainew.path_info.route[i - 1] || + t == p->ainew.path_info.route[i + 1]) { + continue; + } + // Not around a bridge? + if (p->ainew.path_info.route_extra[i] != 0) continue; + if (IsTileType(tile, MP_TUNNELBRIDGE)) continue; + // Is the terrain clear? + if (IsTileType(t, MP_CLEAR) || IsTileType(t, MP_TREES)) { + // If the current tile is on a slope then we do not allow this + if (GetTileSlope(tile, NULL) != SLOPE_FLAT) continue; + // Check if everything went okay.. + r = AiNew_Build_Depot(p, t, ReverseDiagDir(j), 0); + if (CmdFailed(r)) continue; + // Found a spot! + p->ainew.new_cost += r; + p->ainew.depot_tile = t; + p->ainew.depot_direction = ReverseDiagDir(j); // Reverse direction + p->ainew.state = AI_STATE_VERIFY_ROUTE; + return; + } + } + } + + // Failed to find a depot? + p->ainew.state = AI_STATE_NOTHING; +} + + +// This function calculates how many vehicles there are needed on this +// traject. +// It works pretty simple: get the length, see how much we move around +// and hussle that, and you know how many vehicles there are needed. +// It returns the cost for the vehicles +static int AiNew_HowManyVehicles(Player *p) +{ + if (p->ainew.tbt == AI_BUS) { + // For bus-routes we look at the time before we are back in the station + EngineID i; + int length, tiles_a_day; + int amount; + i = AiNew_PickVehicle(p); + if (i == INVALID_ENGINE) return 0; + // Passenger run.. how long is the route? + length = p->ainew.path_info.route_length; + // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! + tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; + // We want a vehicle in a station once a month at least, so, calculate it! + // (the * 2 is because we have 2 stations ;)) + amount = length * 2 * 2 / tiles_a_day / 30; + if (amount == 0) amount = 1; + return amount; + } else if (p->ainew.tbt == AI_TRUCK) { + // For truck-routes we look at the cargo + EngineID i; + int length, amount, tiles_a_day; + int max_cargo; + i = AiNew_PickVehicle(p); + if (i == INVALID_ENGINE) return 0; + // Passenger run.. how long is the route? + length = p->ainew.path_info.route_length; + // Calculating tiles a day a vehicle moves is not easy.. this is how it must be done! + tiles_a_day = RoadVehInfo(i)->max_speed * DAY_TICKS / 256 / 16; + if (p->ainew.from_deliver) { + max_cargo = GetIndustry(p->ainew.from_ic)->total_production[0]; + } else { + max_cargo = GetIndustry(p->ainew.to_ic)->total_production[0]; + } + + // This is because moving 60% is more than we can dream of! + max_cargo *= 0.6; + // We want all the cargo to be gone in a month.. so, we know the cargo it delivers + // we know what the vehicle takes with him, and we know the time it takes him + // to get back here.. now let's do some math! + amount = 2 * length * max_cargo / tiles_a_day / 30 / RoadVehInfo(i)->capacity; + amount += 1; + return amount; + } else { + // Currently not supported + return 0; + } +} + + +// This function checks: +// - If the route went okay +// - Calculates the amount of money needed to build the route +// - Calculates how much vehicles needed for the route +static void AiNew_State_VerifyRoute(Player *p) +{ + int res, i; + assert(p->ainew.state == AI_STATE_VERIFY_ROUTE); + + // Let's calculate the cost of the path.. + // new_cost already contains the cost of the stations + p->ainew.path_info.position = -1; + + do { + p->ainew.path_info.position++; + p->ainew.new_cost += AiNew_Build_RoutePart(p, &p->ainew.path_info, DC_QUERY_COST); + } while (p->ainew.path_info.position != -2); + + // Now we know the price of build station + path. Now check how many vehicles + // we need and what the price for that will be + res = AiNew_HowManyVehicles(p); + // If res == 0, no vehicle was found, or an other problem did occour + if (res == 0) { + p->ainew.state = AI_STATE_NOTHING; + return; + } + p->ainew.amount_veh = res; + p->ainew.cur_veh = 0; + + // Check how much it it going to cost us.. + for (i=0;iainew.new_cost += AiNew_Build_Vehicle(p, 0, DC_QUERY_COST); + } + + // Now we know how much the route is going to cost us + // Check if we have enough money for it! + if (p->ainew.new_cost > p->player_money - AI_MINIMUM_MONEY) { + // Too bad.. + DEBUG(ai, 1, "Insufficient funds to build route (%d)", p->ainew.new_cost); + p->ainew.state = AI_STATE_NOTHING; + return; + } + + // Now we can build the route, check the direction of the stations! + if (p->ainew.from_direction == AI_PATHFINDER_NO_DIRECTION) { + p->ainew.from_direction = AiNew_GetDirection(p->ainew.path_info.route[p->ainew.path_info.route_length-1], p->ainew.path_info.route[p->ainew.path_info.route_length-2]); + } + if (p->ainew.to_direction == AI_PATHFINDER_NO_DIRECTION) { + p->ainew.to_direction = AiNew_GetDirection(p->ainew.path_info.route[0], p->ainew.path_info.route[1]); + } + if (p->ainew.from_tile == AI_STATION_RANGE) + p->ainew.from_tile = p->ainew.path_info.route[p->ainew.path_info.route_length-1]; + if (p->ainew.to_tile == AI_STATION_RANGE) + p->ainew.to_tile = p->ainew.path_info.route[0]; + + p->ainew.state = AI_STATE_BUILD_STATION; + p->ainew.temp = 0; + + DEBUG(ai, 1, "The route is set and buildable, building 0x%X to 0x%X...", p->ainew.from_tile, p->ainew.to_tile); +} + + +// Build the stations +static void AiNew_State_BuildStation(Player *p) +{ + int res = 0; + assert(p->ainew.state == AI_STATE_BUILD_STATION); + if (p->ainew.temp == 0) { + if (!IsTileType(p->ainew.from_tile, MP_STATION)) + res = AiNew_Build_Station(p, p->ainew.tbt, p->ainew.from_tile, 0, 0, p->ainew.from_direction, DC_EXEC); + } else { + if (!IsTileType(p->ainew.to_tile, MP_STATION)) + res = AiNew_Build_Station(p, p->ainew.tbt, p->ainew.to_tile, 0, 0, p->ainew.to_direction, DC_EXEC); + p->ainew.state = AI_STATE_BUILD_PATH; + } + if (CmdFailed(res)) { + DEBUG(ai, 0, "[BuildStation] station could not be built (0x%X)", p->ainew.to_tile); + p->ainew.state = AI_STATE_NOTHING; + // If the first station _was_ build, destroy it + if (p->ainew.temp != 0) + AI_DoCommand(p->ainew.from_tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); + return; + } + p->ainew.temp++; +} + + +// Build the path +static void AiNew_State_BuildPath(Player *p) +{ + assert(p->ainew.state == AI_STATE_BUILD_PATH); + // p->ainew.temp is set to -1 when this function is called for the first time + if (p->ainew.temp == -1) { + DEBUG(ai, 1, "Starting to build new path"); + // Init the counter + p->ainew.counter = (4 - _opt.diff.competitor_speed) * AI_BUILDPATH_PAUSE + 1; + // Set the position to the startingplace (-1 because in a minute we do ++) + p->ainew.path_info.position = -1; + // And don't do this again + p->ainew.temp = 0; + } + // Building goes very fast on normal rate, so we are going to slow it down.. + // By let the counter count from AI_BUILDPATH_PAUSE to 0, we have a nice way :) + if (--p->ainew.counter != 0) return; + p->ainew.counter = (4 - _opt.diff.competitor_speed) * AI_BUILDPATH_PAUSE + 1; + + // Increase the building position + p->ainew.path_info.position++; + // Build route + AiNew_Build_RoutePart(p, &p->ainew.path_info, DC_EXEC); + if (p->ainew.path_info.position == -2) { + // This means we are done building! + + if (p->ainew.tbt == AI_TRUCK && !_patches.roadveh_queue) { + // If they not queue, they have to go up and down to try again at a station... + // We don't want that, so try building some road left or right of the station + int dir1, dir2, dir3; + TileIndex tile; + int i, ret; + for (i=0;i<2;i++) { + if (i == 0) { + tile = p->ainew.from_tile + TileOffsByDiagDir(p->ainew.from_direction); + dir1 = p->ainew.from_direction - 1; + if (dir1 < 0) dir1 = 3; + dir2 = p->ainew.from_direction + 1; + if (dir2 > 3) dir2 = 0; + dir3 = p->ainew.from_direction; + } else { + tile = p->ainew.to_tile + TileOffsByDiagDir(p->ainew.to_direction); + dir1 = p->ainew.to_direction - 1; + if (dir1 < 0) dir1 = 3; + dir2 = p->ainew.to_direction + 1; + if (dir2 > 3) dir2 = 0; + dir3 = p->ainew.to_direction; + } + + ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir1)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + dir1 = TileOffsByDiagDir(dir1); + if (IsTileType(tile + dir1, MP_CLEAR) || IsTileType(tile + dir1, MP_TREES)) { + ret = AI_DoCommand(tile+dir1, AiNew_GetRoadDirection(tile, tile+dir1, tile+dir1+dir1), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + if (IsTileType(tile + dir1 + dir1, MP_CLEAR) || IsTileType(tile + dir1 + dir1, MP_TREES)) + AI_DoCommand(tile+dir1+dir1, AiNew_GetRoadDirection(tile+dir1, tile+dir1+dir1, tile+dir1+dir1+dir1), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + } + } + } + + ret = AI_DoCommand(tile, DiagDirToRoadBits(ReverseDiagDir(dir2)), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + dir2 = TileOffsByDiagDir(dir2); + if (IsTileType(tile + dir2, MP_CLEAR) || IsTileType(tile + dir2, MP_TREES)) { + ret = AI_DoCommand(tile+dir2, AiNew_GetRoadDirection(tile, tile+dir2, tile+dir2+dir2), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + if (IsTileType(tile + dir2 + dir2, MP_CLEAR) || IsTileType(tile + dir2 + dir2, MP_TREES)) + AI_DoCommand(tile+dir2+dir2, AiNew_GetRoadDirection(tile+dir2, tile+dir2+dir2, tile+dir2+dir2+dir2), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + } + } + } + + ret = AI_DoCommand(tile, DiagDirToRoadBits(dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + dir3 = TileOffsByDiagDir(dir3); + if (IsTileType(tile + dir3, MP_CLEAR) || IsTileType(tile + dir3, MP_TREES)) { + ret = AI_DoCommand(tile+dir3, AiNew_GetRoadDirection(tile, tile+dir3, tile+dir3+dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + if (!CmdFailed(ret)) { + if (IsTileType(tile + dir3 + dir3, MP_CLEAR) || IsTileType(tile + dir3 + dir3, MP_TREES)) + AI_DoCommand(tile+dir3+dir3, AiNew_GetRoadDirection(tile+dir3, tile+dir3+dir3, tile+dir3+dir3+dir3), 0, DC_EXEC | DC_NO_WATER, CMD_BUILD_ROAD); + } + } + } + } + } + + DEBUG(ai, 1, "Finished building path, cost: %d", p->ainew.new_cost); + p->ainew.state = AI_STATE_BUILD_DEPOT; + } +} + + +// Builds the depot +static void AiNew_State_BuildDepot(Player *p) +{ + int res = 0; + assert(p->ainew.state == AI_STATE_BUILD_DEPOT); + + if (IsTileType(p->ainew.depot_tile, MP_STREET) && GetRoadTileType(p->ainew.depot_tile) == ROAD_TILE_DEPOT) { + if (IsTileOwner(p->ainew.depot_tile, _current_player)) { + // The depot is already built + p->ainew.state = AI_STATE_BUILD_VEHICLE; + return; + } else { + // There is a depot, but not of our team! :( + p->ainew.state = AI_STATE_NOTHING; + return; + } + } + + // There is a bus on the tile we want to build road on... idle till he is gone! (BAD PERSON! :p) + if (!EnsureNoVehicle(p->ainew.depot_tile + TileOffsByDiagDir(p->ainew.depot_direction))) + return; + + res = AiNew_Build_Depot(p, p->ainew.depot_tile, p->ainew.depot_direction, DC_EXEC); + if (CmdFailed(res)) { + DEBUG(ai, 0, "[BuildDepot] depot could not be built (0x%X)", p->ainew.depot_tile); + p->ainew.state = AI_STATE_NOTHING; + return; + } + + p->ainew.state = AI_STATE_BUILD_VEHICLE; + p->ainew.idle = 10; + p->ainew.veh_main_id = INVALID_VEHICLE; +} + + +// Build vehicles +static void AiNew_State_BuildVehicle(Player *p) +{ + int res; + assert(p->ainew.state == AI_STATE_BUILD_VEHICLE); + + // Check if we need to build a vehicle + if (p->ainew.amount_veh == 0) { + // Nope, we are done! + // This means: we are all done! The route is open.. go back to NOTHING + // He will idle some time and it will all start over again.. :) + p->ainew.state = AI_STATE_ACTION_DONE; + return; + } + if (--p->ainew.idle != 0) return; + // It is realistic that the AI can only build 1 vehicle a day.. + // This makes sure of that! + p->ainew.idle = AI_BUILD_VEHICLE_TIME_BETWEEN; + + // Build the vehicle + res = AiNew_Build_Vehicle(p, p->ainew.depot_tile, DC_EXEC); + if (CmdFailed(res)) { + // This happens when the AI can't build any more vehicles! + p->ainew.state = AI_STATE_NOTHING; + return; + } + // Increase the current counter + p->ainew.cur_veh++; + // Decrease the total counter + p->ainew.amount_veh--; + // Go give some orders! + p->ainew.state = AI_STATE_WAIT_FOR_BUILD; +} + + +// Put the stations in the order list +static void AiNew_State_GiveOrders(Player *p) +{ + int idx; + Order order; + + assert(p->ainew.state == AI_STATE_GIVE_ORDERS); + + if (p->ainew.veh_main_id != INVALID_VEHICLE) { + AI_DoCommand(0, p->ainew.veh_id + (p->ainew.veh_main_id << 16), 0, DC_EXEC, CMD_CLONE_ORDER); + + p->ainew.state = AI_STATE_START_VEHICLE; + return; + } else { + p->ainew.veh_main_id = p->ainew.veh_id; + } + + // Very handy for AI, goto depot.. but yeah, it needs to be activated ;) + if (_patches.gotodepot) { + idx = 0; + order.type = OT_GOTO_DEPOT; + order.flags = OF_UNLOAD; + order.dest = GetDepotByTile(p->ainew.depot_tile)->index; + AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + } + + idx = 0; + order.type = OT_GOTO_STATION; + order.flags = 0; + order.dest = GetStationIndex(p->ainew.to_tile); + if (p->ainew.tbt == AI_TRUCK && p->ainew.to_deliver) + order.flags |= OF_FULL_LOAD; + AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + + idx = 0; + order.type = OT_GOTO_STATION; + order.flags = 0; + order.dest = GetStationIndex(p->ainew.from_tile); + if (p->ainew.tbt == AI_TRUCK && p->ainew.from_deliver) + order.flags |= OF_FULL_LOAD; + AI_DoCommand(0, p->ainew.veh_id + (idx << 16), PackOrder(&order), DC_EXEC, CMD_INSERT_ORDER); + + // Start the engines! + p->ainew.state = AI_STATE_START_VEHICLE; +} + + +// Start the vehicle +static void AiNew_State_StartVehicle(Player *p) +{ + assert(p->ainew.state == AI_STATE_START_VEHICLE); + + // Skip the first order if it is a second vehicle + // This to make vehicles go different ways.. + if (p->ainew.cur_veh & 1) + AI_DoCommand(0, p->ainew.veh_id, 0, DC_EXEC, CMD_SKIP_ORDER); + + // 3, 2, 1... go! (give START_STOP command ;)) + AI_DoCommand(0, p->ainew.veh_id, 0, DC_EXEC, CMD_START_STOP_ROADVEH); + // Try to build an other vehicle (that function will stop building when needed) + p->ainew.idle = 10; + p->ainew.state = AI_STATE_BUILD_VEHICLE; +} + + +// Repays money +static void AiNew_State_RepayMoney(Player *p) +{ + uint i; + + for (i = 0; i < AI_LOAN_REPAY; i++) { + AI_DoCommand(0, 0, 0, DC_EXEC, CMD_DECREASE_LOAN); + } + p->ainew.state = AI_STATE_ACTION_DONE; +} + + +static void AiNew_CheckVehicle(Player *p, Vehicle *v) +{ + // When a vehicle is under the 6 months, we don't check for anything + if (v->age < 180) return; + + // When a vehicle is older then 1 year, it should make money... + if (v->age > 360) { + // If both years together are not more than AI_MINIMUM_ROUTE_PROFIT, + // it is not worth the line I guess... + if (v->profit_last_year + v->profit_this_year < AI_MINIMUM_ROUTE_PROFIT || + (v->reliability * 100 >> 16) < 40) { + // There is a possibility that the route is fucked up... + if (v->cargo_days > AI_VEHICLE_LOST_DAYS) { + // The vehicle is lost.. check the route, or else, get the vehicle + // back to a depot + // TODO: make this piece of code + } + + + // We are already sending him back + if (AiNew_GetSpecialVehicleFlag(p, v) & AI_VEHICLEFLAG_SELL) { + if (v->type == VEH_Road && IsTileDepotType(v->tile, TRANSPORT_ROAD) && + (v->vehstatus&VS_STOPPED)) { + // We are at the depot, sell the vehicle + AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SELL_ROAD_VEH); + } + return; + } + + if (!AiNew_SetSpecialVehicleFlag(p, v, AI_VEHICLEFLAG_SELL)) return; + { + int ret = 0; + if (v->type == VEH_Road) + ret = AI_DoCommand(0, v->index, 0, DC_EXEC, CMD_SEND_ROADVEH_TO_DEPOT); + // This means we can not find a depot :s + // if (CmdFailed(ret)) + } + } + } +} + + +// Checks all vehicles if they are still valid and make money and stuff +static void AiNew_State_CheckAllVehicles(Player *p) +{ + Vehicle *v; + + FOR_ALL_VEHICLES(v) { + if (v->owner != p->index) continue; + // Currently, we only know how to handle road-vehicles + if (v->type != VEH_Road) continue; + + AiNew_CheckVehicle(p, v); + } + + p->ainew.state = AI_STATE_ACTION_DONE; +} + + +// Using the technique simular to the original AI +// Keeps things logical +// It really should be in the same order as the AI_STATE's are! +static AiNew_StateFunction* const _ainew_state[] = { + NULL, + AiNew_State_FirstTime, + AiNew_State_Nothing, + AiNew_State_WakeUp, + AiNew_State_LocateRoute, + AiNew_State_FindStation, + AiNew_State_FindPath, + AiNew_State_FindDepot, + AiNew_State_VerifyRoute, + AiNew_State_BuildStation, + AiNew_State_BuildPath, + AiNew_State_BuildDepot, + AiNew_State_BuildVehicle, + NULL, + AiNew_State_GiveOrders, + AiNew_State_StartVehicle, + AiNew_State_RepayMoney, + AiNew_State_CheckAllVehicles, + AiNew_State_ActionDone, + NULL, +}; + +static void AiNew_OnTick(Player *p) +{ + if (_ainew_state[p->ainew.state] != NULL) + _ainew_state[p->ainew.state](p); +} + + +void AiNewDoGameLoop(Player *p) +{ + if (p->ainew.state == AI_STATE_STARTUP) { + // The AI just got alive! + p->ainew.state = AI_STATE_FIRST_TIME; + p->ainew.tick = 0; + + // Only startup the AI + return; + } + + // We keep a ticker. We use it for competitor_speed + p->ainew.tick++; + + // If we come here, we can do a tick.. do so! + AiNew_OnTick(p); +} diff --git a/src/aircraft_gui.c b/src/aircraft_gui.c deleted file mode 100644 --- a/src/aircraft_gui.c +++ /dev/null @@ -1,350 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "aircraft.h" -#include "debug.h" -#include "functions.h" -#include "table/sprites.h" -#include "table/strings.h" -#include "map.h" -#include "window.h" -#include "gui.h" -#include "vehicle.h" -#include "gfx.h" -#include "command.h" -#include "engine.h" -#include "viewport.h" -#include "player.h" -#include "depot.h" -#include "vehicle_gui.h" -#include "newgrf_engine.h" - - -void CcCloneAircraft(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - if (success) ShowAircraftViewWindow(GetVehicle(_new_vehicle_id)); -} - -static void AircraftDetailsWndProc(Window *w, WindowEvent *e) -{ - switch (e->event) { - case WE_PAINT: { - const Vehicle *v = GetVehicle(w->window_number); - - SetWindowWidgetDisabledState(w, 2, v->owner != _local_player); - - /* Disable service-scroller when interval is set to disabled */ - SetWindowWidgetDisabledState(w, 5, !_patches.servint_aircraft); - SetWindowWidgetDisabledState(w, 6, !_patches.servint_aircraft); - - SetDParam(0, v->string_id); - SetDParam(1, v->unitnumber); - DrawWindowWidgets(w); - - /* Draw running cost */ - { - int year = v->age / 366; - - SetDParam(1, year); - - SetDParam(0, (v->age + 365 < v->max_age) ? STR_AGE : STR_AGE_RED); - SetDParam(2, v->max_age / 366); - SetDParam(3, _price.aircraft_running * AircraftVehInfo(v->engine_type)->running_cost >> 8); - DrawString(2, 15, STR_A00D_AGE_RUNNING_COST_YR, 0); - } - - /* Draw max speed */ - { - SetDParam(0, v->max_speed * 128 / 10); - DrawString(2, 25, STR_A00E_MAX_SPEED, 0); - } - - /* Draw profit */ - { - SetDParam(0, v->profit_this_year); - SetDParam(1, v->profit_last_year); - DrawString(2, 35, STR_A00F_PROFIT_THIS_YEAR_LAST_YEAR, 0); - } - - /* Draw breakdown & reliability */ - { - SetDParam(0, v->reliability * 100 >> 16); - SetDParam(1, v->breakdowns_since_last_service); - DrawString(2, 45, STR_A010_RELIABILITY_BREAKDOWNS, 0); - } - - /* Draw service interval text */ - { - SetDParam(0, v->service_interval); - SetDParam(1, v->date_of_last_service); - DrawString(13, 103, _patches.servint_ispercent?STR_SERVICING_INTERVAL_PERCENT:STR_883C_SERVICING_INTERVAL_DAYS, 0); - } - - DrawAircraftImage(v, 3, 57, INVALID_VEHICLE); - - { - const Vehicle *u; - int y = 57; - - do { - if (v->subtype <= 2) { - SetDParam(0, GetCustomEngineName(v->engine_type)); - SetDParam(1, v->build_year); - SetDParam(2, v->value); - DrawString(60, y, STR_A011_BUILT_VALUE, 0); - y += 10; - - SetDParam(0, v->cargo_type); - SetDParam(1, v->cargo_cap); - u = v->next; - SetDParam(2, u->cargo_type); - SetDParam(3, u->cargo_cap); - DrawString(60, y, (u->cargo_cap != 0) ? STR_A019_CAPACITY : STR_A01A_CAPACITY, 0); - y += 14; - } - - if (v->cargo_count != 0) { - - /* Cargo names (fix pluralness) */ - SetDParam(0, v->cargo_type); - SetDParam(1, v->cargo_count); - SetDParam(2, v->cargo_source); - DrawString(60, y, STR_8813_FROM, 0); - - y += 10; - } - } while ( (v=v->next) != NULL); - } - } break; - - case WE_CLICK: { - int mod; - const Vehicle *v; - switch (e->we.click.widget) { - case 2: /* rename */ - v = GetVehicle(w->window_number); - SetDParam(0, v->unitnumber); - ShowQueryString(v->string_id, STR_A030_NAME_AIRCRAFT, 31, 150, w, CS_ALPHANUMERAL); - break; - case 5: /* increase int */ - mod = _ctrl_pressed? 5 : 10; - goto do_change_service_int; - case 6: /* decrease int */ - mod = _ctrl_pressed?- 5 : -10; -do_change_service_int: - v = GetVehicle(w->window_number); - - mod = GetServiceIntervalClamped(mod + v->service_interval); - if (mod == v->service_interval) return; - - DoCommandP(v->tile, v->index, mod, NULL, CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_018A_CAN_T_CHANGE_SERVICING)); - break; - } - } break; - - case WE_ON_EDIT_TEXT: - if (e->we.edittext.str[0] != '\0') { - _cmd_text = e->we.edittext.str; - DoCommandP(0, w->window_number, 0, NULL, - CMD_NAME_VEHICLE | CMD_MSG(STR_A031_CAN_T_NAME_AIRCRAFT)); - } - break; - } -} - - -static const Widget _aircraft_details_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, -{ WWT_CAPTION, RESIZE_NONE, 14, 11, 349, 0, 13, STR_A00C_DETAILS, STR_018C_WINDOW_TITLE_DRAG_THIS }, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 350, 389, 0, 13, STR_01AA_NAME, STR_A032_NAME_AIRCRAFT }, -{ WWT_PANEL, RESIZE_NONE, 14, 0, 389, 14, 55, 0x0, STR_NULL }, -{ WWT_PANEL, RESIZE_NONE, 14, 0, 389, 56, 101, 0x0, STR_NULL }, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 10, 102, 107, STR_0188, STR_884D_INCREASE_SERVICING_INTERVAL }, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 10, 108, 113, STR_0189, STR_884E_DECREASE_SERVICING_INTERVAL }, -{ WWT_PANEL, RESIZE_NONE, 14, 11, 389, 102, 113, 0x0, STR_NULL }, -{ WIDGETS_END}, -}; - -static const WindowDesc _aircraft_details_desc = { - WDP_AUTO, WDP_AUTO, 390, 114, - WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, - _aircraft_details_widgets, - AircraftDetailsWndProc -}; - - -static void ShowAircraftDetailsWindow(const Vehicle *v) -{ - Window *w; - VehicleID veh = v->index; - - DeleteWindowById(WC_VEHICLE_ORDERS, veh); - DeleteWindowById(WC_VEHICLE_DETAILS, veh); - - w = AllocateWindowDescFront(&_aircraft_details_desc, veh); - w->caption_color = v->owner; -// w->vscroll.cap = 6; -// w->traindetails_d.tab = 0; -} - - -static const Widget _aircraft_view_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, -{ WWT_CAPTION, RESIZE_RIGHT, 14, 11, 237, 0, 13, STR_A00A, STR_018C_WINDOW_TITLE_DRAG_THIS }, -{ WWT_STICKYBOX, RESIZE_LR, 14, 238, 249, 0, 13, 0x0, STR_STICKY_BUTTON }, -{ WWT_PANEL, RESIZE_RB, 14, 0, 231, 14, 103, 0x0, STR_NULL }, -{ WWT_INSET, RESIZE_RB, 14, 2, 229, 16, 101, 0x0, STR_NULL }, -{ WWT_PUSHBTN, RESIZE_RTB, 14, 0, 237, 104, 115, 0x0, STR_A027_CURRENT_AIRCRAFT_ACTION }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 14, 31, SPR_CENTRE_VIEW_VEHICLE, STR_A029_CENTER_MAIN_VIEW_ON_AIRCRAFT }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 32, 49, SPR_SEND_AIRCRAFT_TODEPOT,STR_A02A_SEND_AIRCRAFT_TO_HANGAR }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 50, 67, SPR_REFIT_VEHICLE, STR_A03B_REFIT_AIRCRAFT_TO_CARRY }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 68, 85, SPR_SHOW_ORDERS, STR_A028_SHOW_AIRCRAFT_S_ORDERS }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 86, 103, SPR_SHOW_VEHICLE_DETAILS, STR_A02B_SHOW_AIRCRAFT_DETAILS }, -{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 32, 49, SPR_CLONE_AIRCRAFT, STR_CLONE_AIRCRAFT_INFO }, -{ WWT_PANEL, RESIZE_LRB, 14, 232, 249, 104, 103, 0x0, STR_NULL }, -{ WWT_RESIZEBOX, RESIZE_LRTB, 14, 238, 249, 104, 115, 0x0, STR_NULL }, -{ WIDGETS_END}, -}; - - -static void AircraftViewWndProc(Window *w, WindowEvent *e) -{ - switch (e->event) { - case WE_PAINT: { - const Vehicle *v = GetVehicle(w->window_number); - StringID str; - bool is_localplayer = v->owner == _local_player; - - SetWindowWidgetDisabledState(w, 7, !is_localplayer); - SetWindowWidgetDisabledState(w, 8, !IsAircraftInHangarStopped(v) || !is_localplayer); - SetWindowWidgetDisabledState(w, 11, !is_localplayer); - - - /* draw widgets & caption */ - SetDParam(0, v->string_id); - SetDParam(1, v->unitnumber); - DrawWindowWidgets(w); - - if (v->vehstatus & VS_CRASHED) { - str = STR_8863_CRASHED; - } else if (v->vehstatus & VS_STOPPED) { - str = STR_8861_STOPPED; - } else { - switch (v->current_order.type) { - case OT_GOTO_STATION: { - SetDParam(0, v->current_order.dest); - SetDParam(1, v->cur_speed * 128 / 10); - str = STR_HEADING_FOR_STATION + _patches.vehicle_speed; - } break; - - case OT_GOTO_DEPOT: { - /* Aircrafts always go to a station, even if you say depot */ - SetDParam(0, v->current_order.dest); - SetDParam(1, v->cur_speed * 128 / 10); - if (HASBIT(v->current_order.flags, OFB_HALT_IN_DEPOT) && !HASBIT(v->current_order.flags, OFB_PART_OF_ORDERS)) { - str = STR_HEADING_FOR_HANGAR + _patches.vehicle_speed; - } else { - str = STR_HEADING_FOR_HANGAR_SERVICE + _patches.vehicle_speed; - } - } break; - - case OT_LOADING: - str = STR_882F_LOADING_UNLOADING; - break; - - default: - if (v->num_orders == 0) { - str = STR_NO_ORDERS + _patches.vehicle_speed; - SetDParam(0, v->cur_speed * 128 / 10); - } else { - str = STR_EMPTY; - } - break; - } - } - - /* draw the flag plus orders */ - DrawSprite(v->vehstatus & VS_STOPPED ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, 2, w->widget[5].top + 1); - DrawStringCenteredTruncated(w->widget[5].left + 8, w->widget[5].right, w->widget[5].top + 1, str, 0); - DrawWindowViewport(w); - } break; - - case WE_CLICK: { - const Vehicle *v = GetVehicle(w->window_number); - - switch (e->we.click.widget) { - case 5: /* start stop */ - DoCommandP(v->tile, v->index, 0, NULL, CMD_START_STOP_AIRCRAFT | CMD_MSG(STR_A016_CAN_T_STOP_START_AIRCRAFT)); - break; - case 6: /* center main view */ - ScrollMainWindowTo(v->x_pos, v->y_pos); - break; - case 7: /* goto hangar */ - DoCommandP(v->tile, v->index, _ctrl_pressed ? DEPOT_SERVICE : 0, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_MSG(STR_A012_CAN_T_SEND_AIRCRAFT_TO)); - break; - case 8: /* refit */ - ShowVehicleRefitWindow(v, INVALID_VEH_ORDER_ID); - break; - case 9: /* show orders */ - ShowOrdersWindow(v); - break; - case 10: /* show details */ - ShowAircraftDetailsWindow(v); - break; - case 11: - /* clone vehicle */ - DoCommandP(v->tile, v->index, _ctrl_pressed ? 1 : 0, CcCloneAircraft, CMD_CLONE_VEHICLE | CMD_MSG(STR_A008_CAN_T_BUILD_AIRCRAFT)); - break; - } - } break; - - case WE_RESIZE: - w->viewport->width += e->we.sizing.diff.x; - w->viewport->height += e->we.sizing.diff.y; - w->viewport->virtual_width += e->we.sizing.diff.x; - w->viewport->virtual_height += e->we.sizing.diff.y; - break; - - case WE_DESTROY: - DeleteWindowById(WC_VEHICLE_ORDERS, w->window_number); - DeleteWindowById(WC_VEHICLE_REFIT, w->window_number); - DeleteWindowById(WC_VEHICLE_DETAILS, w->window_number); - break; - - case WE_MOUSELOOP: { - const Vehicle *v = GetVehicle(w->window_number); - bool plane_stopped = IsAircraftInHangarStopped(v); - - /* Widget 7 (send to hangar) must be hidden if the plane is already stopped in hangar. - * Widget 11 (clone) should then be shown, since cloning is allowed only while in hangar and stopped. - * This sytem allows to have two buttons, on top of each other*/ - if (plane_stopped != IsWindowWidgetHidden(w, 7) || plane_stopped == IsWindowWidgetHidden(w, 11)) { - SetWindowWidgetHiddenState(w, 7, plane_stopped); // send to hangar - SetWindowWidgetHiddenState(w, 11, !plane_stopped); // clone - SetWindowDirty(w); - } - } break; - } -} - - -static const WindowDesc _aircraft_view_desc = { - WDP_AUTO, WDP_AUTO, 250, 116, - WC_VEHICLE_VIEW ,0, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, - _aircraft_view_widgets, - AircraftViewWndProc -}; - - -void ShowAircraftViewWindow(const Vehicle *v) -{ - Window *w = AllocateWindowDescFront(&_aircraft_view_desc, v->index); - - if (w != NULL) { - w->caption_color = v->owner; - AssignWindowViewport(w, 3, 17, 0xE2, 0x54, w->window_number | (1 << 31), 0); - } -} diff --git a/src/aircraft_gui.cpp b/src/aircraft_gui.cpp new file mode 100644 --- /dev/null +++ b/src/aircraft_gui.cpp @@ -0,0 +1,350 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "aircraft.h" +#include "debug.h" +#include "functions.h" +#include "table/sprites.h" +#include "table/strings.h" +#include "map.h" +#include "window.h" +#include "gui.h" +#include "vehicle.h" +#include "gfx.h" +#include "command.h" +#include "engine.h" +#include "viewport.h" +#include "player.h" +#include "depot.h" +#include "vehicle_gui.h" +#include "newgrf_engine.h" + + +void CcCloneAircraft(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + if (success) ShowAircraftViewWindow(GetVehicle(_new_vehicle_id)); +} + +static void AircraftDetailsWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + const Vehicle *v = GetVehicle(w->window_number); + + SetWindowWidgetDisabledState(w, 2, v->owner != _local_player); + + /* Disable service-scroller when interval is set to disabled */ + SetWindowWidgetDisabledState(w, 5, !_patches.servint_aircraft); + SetWindowWidgetDisabledState(w, 6, !_patches.servint_aircraft); + + SetDParam(0, v->string_id); + SetDParam(1, v->unitnumber); + DrawWindowWidgets(w); + + /* Draw running cost */ + { + int year = v->age / 366; + + SetDParam(1, year); + + SetDParam(0, (v->age + 365 < v->max_age) ? STR_AGE : STR_AGE_RED); + SetDParam(2, v->max_age / 366); + SetDParam(3, _price.aircraft_running * AircraftVehInfo(v->engine_type)->running_cost >> 8); + DrawString(2, 15, STR_A00D_AGE_RUNNING_COST_YR, 0); + } + + /* Draw max speed */ + { + SetDParam(0, v->max_speed * 128 / 10); + DrawString(2, 25, STR_A00E_MAX_SPEED, 0); + } + + /* Draw profit */ + { + SetDParam(0, v->profit_this_year); + SetDParam(1, v->profit_last_year); + DrawString(2, 35, STR_A00F_PROFIT_THIS_YEAR_LAST_YEAR, 0); + } + + /* Draw breakdown & reliability */ + { + SetDParam(0, v->reliability * 100 >> 16); + SetDParam(1, v->breakdowns_since_last_service); + DrawString(2, 45, STR_A010_RELIABILITY_BREAKDOWNS, 0); + } + + /* Draw service interval text */ + { + SetDParam(0, v->service_interval); + SetDParam(1, v->date_of_last_service); + DrawString(13, 103, _patches.servint_ispercent?STR_SERVICING_INTERVAL_PERCENT:STR_883C_SERVICING_INTERVAL_DAYS, 0); + } + + DrawAircraftImage(v, 3, 57, INVALID_VEHICLE); + + { + const Vehicle *u; + int y = 57; + + do { + if (v->subtype <= 2) { + SetDParam(0, GetCustomEngineName(v->engine_type)); + SetDParam(1, v->build_year); + SetDParam(2, v->value); + DrawString(60, y, STR_A011_BUILT_VALUE, 0); + y += 10; + + SetDParam(0, v->cargo_type); + SetDParam(1, v->cargo_cap); + u = v->next; + SetDParam(2, u->cargo_type); + SetDParam(3, u->cargo_cap); + DrawString(60, y, (u->cargo_cap != 0) ? STR_A019_CAPACITY : STR_A01A_CAPACITY, 0); + y += 14; + } + + if (v->cargo_count != 0) { + + /* Cargo names (fix pluralness) */ + SetDParam(0, v->cargo_type); + SetDParam(1, v->cargo_count); + SetDParam(2, v->cargo_source); + DrawString(60, y, STR_8813_FROM, 0); + + y += 10; + } + } while ( (v=v->next) != NULL); + } + } break; + + case WE_CLICK: { + int mod; + const Vehicle *v; + switch (e->we.click.widget) { + case 2: /* rename */ + v = GetVehicle(w->window_number); + SetDParam(0, v->unitnumber); + ShowQueryString(v->string_id, STR_A030_NAME_AIRCRAFT, 31, 150, w, CS_ALPHANUMERAL); + break; + case 5: /* increase int */ + mod = _ctrl_pressed? 5 : 10; + goto do_change_service_int; + case 6: /* decrease int */ + mod = _ctrl_pressed?- 5 : -10; +do_change_service_int: + v = GetVehicle(w->window_number); + + mod = GetServiceIntervalClamped(mod + v->service_interval); + if (mod == v->service_interval) return; + + DoCommandP(v->tile, v->index, mod, NULL, CMD_CHANGE_SERVICE_INT | CMD_MSG(STR_018A_CAN_T_CHANGE_SERVICING)); + break; + } + } break; + + case WE_ON_EDIT_TEXT: + if (e->we.edittext.str[0] != '\0') { + _cmd_text = e->we.edittext.str; + DoCommandP(0, w->window_number, 0, NULL, + CMD_NAME_VEHICLE | CMD_MSG(STR_A031_CAN_T_NAME_AIRCRAFT)); + } + break; + } +} + + +static const Widget _aircraft_details_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, RESIZE_NONE, 14, 11, 349, 0, 13, STR_A00C_DETAILS, STR_018C_WINDOW_TITLE_DRAG_THIS }, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 350, 389, 0, 13, STR_01AA_NAME, STR_A032_NAME_AIRCRAFT }, +{ WWT_PANEL, RESIZE_NONE, 14, 0, 389, 14, 55, 0x0, STR_NULL }, +{ WWT_PANEL, RESIZE_NONE, 14, 0, 389, 56, 101, 0x0, STR_NULL }, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 10, 102, 107, STR_0188, STR_884D_INCREASE_SERVICING_INTERVAL }, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 10, 108, 113, STR_0189, STR_884E_DECREASE_SERVICING_INTERVAL }, +{ WWT_PANEL, RESIZE_NONE, 14, 11, 389, 102, 113, 0x0, STR_NULL }, +{ WIDGETS_END}, +}; + +static const WindowDesc _aircraft_details_desc = { + WDP_AUTO, WDP_AUTO, 390, 114, + WC_VEHICLE_DETAILS, WC_VEHICLE_VIEW, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _aircraft_details_widgets, + AircraftDetailsWndProc +}; + + +static void ShowAircraftDetailsWindow(const Vehicle *v) +{ + Window *w; + VehicleID veh = v->index; + + DeleteWindowById(WC_VEHICLE_ORDERS, veh); + DeleteWindowById(WC_VEHICLE_DETAILS, veh); + + w = AllocateWindowDescFront(&_aircraft_details_desc, veh); + w->caption_color = v->owner; +// w->vscroll.cap = 6; +// w->traindetails_d.tab = 0; +} + + +static const Widget _aircraft_view_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, RESIZE_RIGHT, 14, 11, 237, 0, 13, STR_A00A, STR_018C_WINDOW_TITLE_DRAG_THIS }, +{ WWT_STICKYBOX, RESIZE_LR, 14, 238, 249, 0, 13, 0x0, STR_STICKY_BUTTON }, +{ WWT_PANEL, RESIZE_RB, 14, 0, 231, 14, 103, 0x0, STR_NULL }, +{ WWT_INSET, RESIZE_RB, 14, 2, 229, 16, 101, 0x0, STR_NULL }, +{ WWT_PUSHBTN, RESIZE_RTB, 14, 0, 237, 104, 115, 0x0, STR_A027_CURRENT_AIRCRAFT_ACTION }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 14, 31, SPR_CENTRE_VIEW_VEHICLE, STR_A029_CENTER_MAIN_VIEW_ON_AIRCRAFT }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 32, 49, SPR_SEND_AIRCRAFT_TODEPOT,STR_A02A_SEND_AIRCRAFT_TO_HANGAR }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 50, 67, SPR_REFIT_VEHICLE, STR_A03B_REFIT_AIRCRAFT_TO_CARRY }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 68, 85, SPR_SHOW_ORDERS, STR_A028_SHOW_AIRCRAFT_S_ORDERS }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 86, 103, SPR_SHOW_VEHICLE_DETAILS, STR_A02B_SHOW_AIRCRAFT_DETAILS }, +{ WWT_PUSHIMGBTN, RESIZE_LR, 14, 232, 249, 32, 49, SPR_CLONE_AIRCRAFT, STR_CLONE_AIRCRAFT_INFO }, +{ WWT_PANEL, RESIZE_LRB, 14, 232, 249, 104, 103, 0x0, STR_NULL }, +{ WWT_RESIZEBOX, RESIZE_LRTB, 14, 238, 249, 104, 115, 0x0, STR_NULL }, +{ WIDGETS_END}, +}; + + +static void AircraftViewWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + const Vehicle *v = GetVehicle(w->window_number); + StringID str; + bool is_localplayer = v->owner == _local_player; + + SetWindowWidgetDisabledState(w, 7, !is_localplayer); + SetWindowWidgetDisabledState(w, 8, !IsAircraftInHangarStopped(v) || !is_localplayer); + SetWindowWidgetDisabledState(w, 11, !is_localplayer); + + + /* draw widgets & caption */ + SetDParam(0, v->string_id); + SetDParam(1, v->unitnumber); + DrawWindowWidgets(w); + + if (v->vehstatus & VS_CRASHED) { + str = STR_8863_CRASHED; + } else if (v->vehstatus & VS_STOPPED) { + str = STR_8861_STOPPED; + } else { + switch (v->current_order.type) { + case OT_GOTO_STATION: { + SetDParam(0, v->current_order.dest); + SetDParam(1, v->cur_speed * 128 / 10); + str = STR_HEADING_FOR_STATION + _patches.vehicle_speed; + } break; + + case OT_GOTO_DEPOT: { + /* Aircrafts always go to a station, even if you say depot */ + SetDParam(0, v->current_order.dest); + SetDParam(1, v->cur_speed * 128 / 10); + if (HASBIT(v->current_order.flags, OFB_HALT_IN_DEPOT) && !HASBIT(v->current_order.flags, OFB_PART_OF_ORDERS)) { + str = STR_HEADING_FOR_HANGAR + _patches.vehicle_speed; + } else { + str = STR_HEADING_FOR_HANGAR_SERVICE + _patches.vehicle_speed; + } + } break; + + case OT_LOADING: + str = STR_882F_LOADING_UNLOADING; + break; + + default: + if (v->num_orders == 0) { + str = STR_NO_ORDERS + _patches.vehicle_speed; + SetDParam(0, v->cur_speed * 128 / 10); + } else { + str = STR_EMPTY; + } + break; + } + } + + /* draw the flag plus orders */ + DrawSprite(v->vehstatus & VS_STOPPED ? SPR_FLAG_VEH_STOPPED : SPR_FLAG_VEH_RUNNING, 2, w->widget[5].top + 1); + DrawStringCenteredTruncated(w->widget[5].left + 8, w->widget[5].right, w->widget[5].top + 1, str, 0); + DrawWindowViewport(w); + } break; + + case WE_CLICK: { + const Vehicle *v = GetVehicle(w->window_number); + + switch (e->we.click.widget) { + case 5: /* start stop */ + DoCommandP(v->tile, v->index, 0, NULL, CMD_START_STOP_AIRCRAFT | CMD_MSG(STR_A016_CAN_T_STOP_START_AIRCRAFT)); + break; + case 6: /* center main view */ + ScrollMainWindowTo(v->x_pos, v->y_pos); + break; + case 7: /* goto hangar */ + DoCommandP(v->tile, v->index, _ctrl_pressed ? DEPOT_SERVICE : 0, NULL, CMD_SEND_AIRCRAFT_TO_HANGAR | CMD_MSG(STR_A012_CAN_T_SEND_AIRCRAFT_TO)); + break; + case 8: /* refit */ + ShowVehicleRefitWindow(v, INVALID_VEH_ORDER_ID); + break; + case 9: /* show orders */ + ShowOrdersWindow(v); + break; + case 10: /* show details */ + ShowAircraftDetailsWindow(v); + break; + case 11: + /* clone vehicle */ + DoCommandP(v->tile, v->index, _ctrl_pressed ? 1 : 0, CcCloneAircraft, CMD_CLONE_VEHICLE | CMD_MSG(STR_A008_CAN_T_BUILD_AIRCRAFT)); + break; + } + } break; + + case WE_RESIZE: + w->viewport->width += e->we.sizing.diff.x; + w->viewport->height += e->we.sizing.diff.y; + w->viewport->virtual_width += e->we.sizing.diff.x; + w->viewport->virtual_height += e->we.sizing.diff.y; + break; + + case WE_DESTROY: + DeleteWindowById(WC_VEHICLE_ORDERS, w->window_number); + DeleteWindowById(WC_VEHICLE_REFIT, w->window_number); + DeleteWindowById(WC_VEHICLE_DETAILS, w->window_number); + break; + + case WE_MOUSELOOP: { + const Vehicle *v = GetVehicle(w->window_number); + bool plane_stopped = IsAircraftInHangarStopped(v); + + /* Widget 7 (send to hangar) must be hidden if the plane is already stopped in hangar. + * Widget 11 (clone) should then be shown, since cloning is allowed only while in hangar and stopped. + * This sytem allows to have two buttons, on top of each other*/ + if (plane_stopped != IsWindowWidgetHidden(w, 7) || plane_stopped == IsWindowWidgetHidden(w, 11)) { + SetWindowWidgetHiddenState(w, 7, plane_stopped); // send to hangar + SetWindowWidgetHiddenState(w, 11, !plane_stopped); // clone + SetWindowDirty(w); + } + } break; + } +} + + +static const WindowDesc _aircraft_view_desc = { + WDP_AUTO, WDP_AUTO, 250, 116, + WC_VEHICLE_VIEW ,0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE, + _aircraft_view_widgets, + AircraftViewWndProc +}; + + +void ShowAircraftViewWindow(const Vehicle *v) +{ + Window *w = AllocateWindowDescFront(&_aircraft_view_desc, v->index); + + if (w != NULL) { + w->caption_color = v->owner; + AssignWindowViewport(w, 3, 17, 0xE2, 0x54, w->window_number | (1 << 31), 0); + } +} diff --git a/src/airport.c b/src/airport.c deleted file mode 100644 --- a/src/airport.c +++ /dev/null @@ -1,489 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "debug.h" -#include "map.h" -#include "airport.h" -#include "macros.h" -#include "variables.h" -#include "airport_movement.h" -#include "date.h" - -/* Uncomment this to print out a full report of the airport-structure - * You should either use - * - true: full-report, print out every state and choice with string-names - * OR - * - false: give a summarized report which only shows current and next position */ -//#define DEBUG_AIRPORT false - -static AirportFTAClass *CountryAirport; -static AirportFTAClass *CityAirport; -static AirportFTAClass *Oilrig; -static AirportFTAClass *Heliport; -static AirportFTAClass *MetropolitanAirport; -static AirportFTAClass *InternationalAirport; -static AirportFTAClass *CommuterAirport; -static AirportFTAClass *HeliDepot; -static AirportFTAClass *IntercontinentalAirport; -static AirportFTAClass *HeliStation; - -static void AirportFTAClass_Constructor(AirportFTAClass *apc, - const byte *terminals, const byte *helipads, - const byte entry_point, const byte acc_planes, - const AirportFTAbuildup *apFA, - const TileIndexDiffC *depots, const byte nof_depots, - uint size_x, uint size_y -); -static void AirportFTAClass_Destructor(AirportFTAClass *apc); - -static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA); -static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA); -static byte AirportGetTerminalCount(const byte *terminals, byte *groups); -static byte AirportTestFTA(const AirportFTAClass *apc); - -#ifdef DEBUG_AIRPORT -static void AirportPrintOut(const AirportFTAClass *apc, bool full_report); -#endif /* DEBUG_AIRPORT */ - -void InitializeAirports(void) -{ - // country airport - CountryAirport = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - CountryAirport, - _airport_terminal_country, - NULL, - 16, - ALL, - _airport_fta_country, - _airport_depots_country, - lengthof(_airport_depots_country), - 4, 3 - ); - - // city airport - CityAirport = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - CityAirport, - _airport_terminal_city, - NULL, - 19, - ALL, - _airport_fta_city, - _airport_depots_city, - lengthof(_airport_depots_city), - 6, 6 - ); - - // metropolitan airport - MetropolitanAirport = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - MetropolitanAirport, - _airport_terminal_metropolitan, - NULL, - 20, - ALL, - _airport_fta_metropolitan, - _airport_depots_metropolitan, - lengthof(_airport_depots_metropolitan), - 6, 6 - ); - - // international airport - InternationalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - InternationalAirport, - _airport_terminal_international, - _airport_helipad_international, - 37, - ALL, - _airport_fta_international, - _airport_depots_international, - lengthof(_airport_depots_international), - 7, 7 - ); - - // intercontintental airport - IntercontinentalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - IntercontinentalAirport, - _airport_terminal_intercontinental, - _airport_helipad_intercontinental, - 43, - ALL, - _airport_fta_intercontinental, - _airport_depots_intercontinental, - lengthof(_airport_depots_intercontinental), - 9,11 - ); - - // heliport, oilrig - Heliport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - Heliport, - NULL, - _airport_helipad_heliport_oilrig, - 7, - HELICOPTERS_ONLY, - _airport_fta_heliport_oilrig, - NULL, - 0, - 1, 1 - ); - - Oilrig = Heliport; // exactly the same structure for heliport/oilrig, so share state machine - - // commuter airport - CommuterAirport = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - CommuterAirport, - _airport_terminal_commuter, - _airport_helipad_commuter, - 22, - ALL, - _airport_fta_commuter, - _airport_depots_commuter, - lengthof(_airport_depots_commuter), - 5,4 - ); - - // helidepot airport - HeliDepot = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - HeliDepot, - NULL, - _airport_helipad_helidepot, - 4, - HELICOPTERS_ONLY, - _airport_fta_helidepot, - _airport_depots_helidepot, - lengthof(_airport_depots_helidepot), - 2,2 - ); - - // helistation airport - HeliStation = malloc(sizeof(AirportFTAClass)); - - AirportFTAClass_Constructor( - HeliStation, - NULL, - _airport_helipad_helistation, - 25, - HELICOPTERS_ONLY, - _airport_fta_helistation, - _airport_depots_helistation, - lengthof(_airport_depots_helistation), - 4,2 - ); - -} - -void UnInitializeAirports(void) -{ - AirportFTAClass_Destructor(CountryAirport); - AirportFTAClass_Destructor(CityAirport); - AirportFTAClass_Destructor(Heliport); - AirportFTAClass_Destructor(MetropolitanAirport); - AirportFTAClass_Destructor(InternationalAirport); - AirportFTAClass_Destructor(CommuterAirport); - AirportFTAClass_Destructor(HeliDepot); - AirportFTAClass_Destructor(IntercontinentalAirport); - AirportFTAClass_Destructor(HeliStation); -} - -static void AirportFTAClass_Constructor(AirportFTAClass *apc, - const byte *terminals, const byte *helipads, - const byte entry_point, const byte acc_planes, - const AirportFTAbuildup *apFA, - const TileIndexDiffC *depots, const byte nof_depots, - uint size_x, uint size_y -) -{ - byte nofterminals, nofhelipads; - byte nofterminalgroups, nofhelipadgroups; - - apc->size_x = size_x; - apc->size_y = size_y; - - /* Set up the terminal and helipad count for an airport. - * TODO: If there are more than 10 terminals or 4 helipads, internal variables - * need to be changed, so don't allow that for now */ - nofterminals = AirportGetTerminalCount(terminals, &nofterminalgroups); - if (nofterminals > MAX_TERMINALS) { - DEBUG(misc, 0, "[Ap] only a maximum of %d terminals are supported (requested %d)", MAX_TERMINALS, nofterminals); - assert(nofterminals <= MAX_TERMINALS); - } - apc->terminals = terminals; - - nofhelipads = AirportGetTerminalCount(helipads, &nofhelipadgroups); - if (nofhelipads > MAX_HELIPADS) { - DEBUG(misc, 0, "[Ap] only a maximum of %d helipads are supported (requested %d)", MAX_HELIPADS, nofhelipads); - assert(nofhelipads <= MAX_HELIPADS); - } - apc->helipads = helipads; - - /* Get the number of elements from the source table. We also double check this - * with the entry point which must be within bounds and use this information - * later on to build and validate the state machine */ - apc->nofelements = AirportGetNofElements(apFA); - if (entry_point >= apc->nofelements) { - DEBUG(misc, 0, "[Ap] entry (%d) must be within the airport (maximum %d)", entry_point, apc->nofelements); - assert(entry_point < apc->nofelements); - } - - apc->acc_planes = acc_planes; - apc->entry_point = entry_point; - apc->airport_depots = depots; - apc->nof_depots = nof_depots; - - /* Build the state machine itself */ - AirportBuildAutomata(apc, apFA); - DEBUG(misc, 2, "[Ap] #count %3d; #term %2d (%dgrp); #helipad %2d (%dgrp); entry %3d", - apc->nofelements, nofterminals, nofterminalgroups, nofhelipads, nofhelipadgroups, apc->entry_point); - - /* Test if everything went allright. This is only a rude static test checking - * the symantic correctness. By no means does passing the test mean that the - * airport is working correctly or will not deadlock for example */ - { byte ret = AirportTestFTA(apc); - if (ret != MAX_ELEMENTS) DEBUG(misc, 0, "[Ap] problem with element: %d", ret - 1); - assert(ret == MAX_ELEMENTS); - } - -#ifdef DEBUG_AIRPORT - AirportPrintOut(apc, DEBUG_AIRPORT); -#endif -} - -static void AirportFTAClass_Destructor(AirportFTAClass *apc) -{ - int i; - AirportFTA *current, *next; - - for (i = 0; i < apc->nofelements; i++) { - current = apc->layout[i].next; - while (current != NULL) { - next = current->next; - free(current); - current = next; - }; - } - free(apc->layout); - free(apc); -} - -/** Get the number of elements of a source Airport state automata - * Since it is actually just a big array of AirportFTA types, we only - * know one element from the other by differing 'position' identifiers */ -static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA) -{ - int i; - uint16 nofelements = 0; - int temp = apFA[0].position; - - for (i = 0; i < MAX_ELEMENTS; i++) { - if (temp != apFA[i].position) { - nofelements++; - temp = apFA[i].position; - } - if (apFA[i].position == MAX_ELEMENTS) break; - } - return nofelements; -} - -/* We calculate the terminal/helipod count based on the data passed to us - * This data (terminals) contains an index as a first element as to how many - * groups there are, and then the number of terminals for each group */ -static byte AirportGetTerminalCount(const byte *terminals, byte *groups) -{ - byte i; - byte nof_terminals = 0; - *groups = 0; - - if (terminals != NULL) { - i = terminals[0]; - *groups = i; - while (i-- > 0) { - terminals++; - assert(*terminals != 0); // no empty groups please - nof_terminals += *terminals; - } - } - return nof_terminals; -} - -static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA) -{ - AirportFTA *current; - AirportFTA *FAutomata = malloc(sizeof(AirportFTA) * apc->nofelements); - uint16 internalcounter = 0; - uint16 i; - - apc->layout = FAutomata; - for (i = 0; i < apc->nofelements; i++) { - current = &apc->layout[i]; - current->position = apFA[internalcounter].position; - current->heading = apFA[internalcounter].heading; - current->block = apFA[internalcounter].block; - current->next_position = apFA[internalcounter].next; - - // outgoing nodes from the same position, create linked list - while (current->position == apFA[internalcounter + 1].position) { - AirportFTA *newNode = malloc(sizeof(AirportFTA)); - - newNode->position = apFA[internalcounter + 1].position; - newNode->heading = apFA[internalcounter + 1].heading; - newNode->block = apFA[internalcounter + 1].block; - newNode->next_position = apFA[internalcounter + 1].next; - // create link - current->next = newNode; - current = current->next; - internalcounter++; - } // while - current->next = NULL; - internalcounter++; - } -} - -static byte AirportTestFTA(const AirportFTAClass *apc) -{ - byte position, i, next_position; - AirportFTA *current, *first; - next_position = 0; - - for (i = 0; i < apc->nofelements; i++) { - position = apc->layout[i].position; - if (position != next_position) return i; - current = first = &apc->layout[i]; - - for (; current != NULL; current = current->next) { - /* A heading must always be valid. The only exceptions are - * - multiple choices as start, identified by a special value of 255 - * - terminal group which is identified by a special value of 255 */ - if (current->heading > MAX_HEADINGS) { - if (current->heading != 255) return i; - if (current == first && current->next == NULL) return i; - if (current != first && current->next_position > apc->terminals[0]) return i; - } - - /* If there is only one choice, it must be at the end */ - if (current->heading == 0 && current->next != NULL) return i; - /* Obviously the elements of the linked list must have the same identifier */ - if (position != current->position) return i; - /* A next position must be within bounds */ - if (current->next_position >= apc->nofelements) return i; - } - next_position++; - } - return MAX_ELEMENTS; -} - -#ifdef DEBUG_AIRPORT -static const char* const _airport_heading_strings[] = { - "TO_ALL", - "HANGAR", - "TERM1", - "TERM2", - "TERM3", - "TERM4", - "TERM5", - "TERM6", - "HELIPAD1", - "HELIPAD2", - "TAKEOFF", - "STARTTAKEOFF", - "ENDTAKEOFF", - "HELITAKEOFF", - "FLYING", - "LANDING", - "ENDLANDING", - "HELILANDING", - "HELIENDLANDING", - "TERM7", - "TERM8", - "HELIPAD3", - "HELIPAD4", - "DUMMY" // extra heading for 255 -}; - -static uint AirportBlockToString(uint32 block) -{ - uint i = 0; - if (block & 0xffff0000) { block >>= 16; i += 16; } - if (block & 0x0000ff00) { block >>= 8; i += 8; } - if (block & 0x000000f0) { block >>= 4; i += 4; } - if (block & 0x0000000c) { block >>= 2; i += 2; } - if (block & 0x00000002) { i += 1; } - return i; -} - -static void AirportPrintOut(const AirportFTAClass *apc, bool full_report) -{ - uint16 i; - - if (!full_report) printf("(P = Current Position; NP = Next Position)\n"); - - for (i = 0; i < apc->nofelements; i++) { - AirportFTA *current = &apc->layout[i]; - - for (; current != NULL; current = current->next) { - if (full_report) { - byte heading = (current->heading == 255) ? MAX_HEADINGS + 1 : current->heading; - printf("\tPos:%2d NPos:%2d Heading:%15s Block:%2d\n", current->position, - current->next_position, _airport_heading_strings[heading], - AirportBlockToString(current->block)); - } else { - printf("P:%2d NP:%2d", current->position, current->next_position); - } - } - printf("\n"); - } -} -#endif - -const AirportFTAClass *GetAirport(const byte airport_type) -{ - //FIXME -- AircraftNextAirportPos_and_Order -> Needs something nicer, don't like this code - // needs constant change if more airports are added - switch (airport_type) { - default: NOT_REACHED(); - case AT_SMALL: return CountryAirport; - case AT_LARGE: return CityAirport; - case AT_METROPOLITAN: return MetropolitanAirport; - case AT_HELIPORT: return Heliport; - case AT_OILRIG: return Oilrig; - case AT_INTERNATIONAL: return InternationalAirport; - case AT_COMMUTER: return CommuterAirport; - case AT_HELIDEPOT: return HeliDepot; - case AT_INTERCON: return IntercontinentalAirport; - case AT_HELISTATION: return HeliStation; - } -} - -const AirportMovingData *GetAirportMovingData(byte airport_type, byte position) -{ - assert(airport_type < lengthof(_airport_moving_datas)); - assert(position < GetAirport(airport_type)->nofelements); - return &_airport_moving_datas[airport_type][position]; -} - -uint32 GetValidAirports(void) -{ - uint32 bytemask = _avail_aircraft; /// sets the first 3 bytes, 0 - 2, @see AdjustAvailAircraft() - - if (_cur_year >= 1980) SETBIT(bytemask, 3); // metropolitan airport - if (_cur_year >= 1990) SETBIT(bytemask, 4); // international airport - if (_cur_year >= 1983) SETBIT(bytemask, 5); // commuter airport - if (_cur_year >= 1976) SETBIT(bytemask, 6); // helidepot - if (_cur_year >= 2002) SETBIT(bytemask, 7); // intercontinental airport - if (_cur_year >= 1980) SETBIT(bytemask, 8); // helistation - return bytemask; -} diff --git a/src/airport.cpp b/src/airport.cpp new file mode 100644 --- /dev/null +++ b/src/airport.cpp @@ -0,0 +1,489 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "debug.h" +#include "map.h" +#include "airport.h" +#include "macros.h" +#include "variables.h" +#include "airport_movement.h" +#include "date.h" + +/* Uncomment this to print out a full report of the airport-structure + * You should either use + * - true: full-report, print out every state and choice with string-names + * OR + * - false: give a summarized report which only shows current and next position */ +//#define DEBUG_AIRPORT false + +static AirportFTAClass *CountryAirport; +static AirportFTAClass *CityAirport; +static AirportFTAClass *Oilrig; +static AirportFTAClass *Heliport; +static AirportFTAClass *MetropolitanAirport; +static AirportFTAClass *InternationalAirport; +static AirportFTAClass *CommuterAirport; +static AirportFTAClass *HeliDepot; +static AirportFTAClass *IntercontinentalAirport; +static AirportFTAClass *HeliStation; + +static void AirportFTAClass_Constructor(AirportFTAClass *apc, + const byte *terminals, const byte *helipads, + const byte entry_point, const byte acc_planes, + const AirportFTAbuildup *apFA, + const TileIndexDiffC *depots, const byte nof_depots, + uint size_x, uint size_y +); +static void AirportFTAClass_Destructor(AirportFTAClass *apc); + +static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA); +static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA); +static byte AirportGetTerminalCount(const byte *terminals, byte *groups); +static byte AirportTestFTA(const AirportFTAClass *apc); + +#ifdef DEBUG_AIRPORT +static void AirportPrintOut(const AirportFTAClass *apc, bool full_report); +#endif /* DEBUG_AIRPORT */ + +void InitializeAirports(void) +{ + // country airport + CountryAirport = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + CountryAirport, + _airport_terminal_country, + NULL, + 16, + ALL, + _airport_fta_country, + _airport_depots_country, + lengthof(_airport_depots_country), + 4, 3 + ); + + // city airport + CityAirport = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + CityAirport, + _airport_terminal_city, + NULL, + 19, + ALL, + _airport_fta_city, + _airport_depots_city, + lengthof(_airport_depots_city), + 6, 6 + ); + + // metropolitan airport + MetropolitanAirport = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + MetropolitanAirport, + _airport_terminal_metropolitan, + NULL, + 20, + ALL, + _airport_fta_metropolitan, + _airport_depots_metropolitan, + lengthof(_airport_depots_metropolitan), + 6, 6 + ); + + // international airport + InternationalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + InternationalAirport, + _airport_terminal_international, + _airport_helipad_international, + 37, + ALL, + _airport_fta_international, + _airport_depots_international, + lengthof(_airport_depots_international), + 7, 7 + ); + + // intercontintental airport + IntercontinentalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + IntercontinentalAirport, + _airport_terminal_intercontinental, + _airport_helipad_intercontinental, + 43, + ALL, + _airport_fta_intercontinental, + _airport_depots_intercontinental, + lengthof(_airport_depots_intercontinental), + 9,11 + ); + + // heliport, oilrig + Heliport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + Heliport, + NULL, + _airport_helipad_heliport_oilrig, + 7, + HELICOPTERS_ONLY, + _airport_fta_heliport_oilrig, + NULL, + 0, + 1, 1 + ); + + Oilrig = Heliport; // exactly the same structure for heliport/oilrig, so share state machine + + // commuter airport + CommuterAirport = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + CommuterAirport, + _airport_terminal_commuter, + _airport_helipad_commuter, + 22, + ALL, + _airport_fta_commuter, + _airport_depots_commuter, + lengthof(_airport_depots_commuter), + 5,4 + ); + + // helidepot airport + HeliDepot = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + HeliDepot, + NULL, + _airport_helipad_helidepot, + 4, + HELICOPTERS_ONLY, + _airport_fta_helidepot, + _airport_depots_helidepot, + lengthof(_airport_depots_helidepot), + 2,2 + ); + + // helistation airport + HeliStation = malloc(sizeof(AirportFTAClass)); + + AirportFTAClass_Constructor( + HeliStation, + NULL, + _airport_helipad_helistation, + 25, + HELICOPTERS_ONLY, + _airport_fta_helistation, + _airport_depots_helistation, + lengthof(_airport_depots_helistation), + 4,2 + ); + +} + +void UnInitializeAirports(void) +{ + AirportFTAClass_Destructor(CountryAirport); + AirportFTAClass_Destructor(CityAirport); + AirportFTAClass_Destructor(Heliport); + AirportFTAClass_Destructor(MetropolitanAirport); + AirportFTAClass_Destructor(InternationalAirport); + AirportFTAClass_Destructor(CommuterAirport); + AirportFTAClass_Destructor(HeliDepot); + AirportFTAClass_Destructor(IntercontinentalAirport); + AirportFTAClass_Destructor(HeliStation); +} + +static void AirportFTAClass_Constructor(AirportFTAClass *apc, + const byte *terminals, const byte *helipads, + const byte entry_point, const byte acc_planes, + const AirportFTAbuildup *apFA, + const TileIndexDiffC *depots, const byte nof_depots, + uint size_x, uint size_y +) +{ + byte nofterminals, nofhelipads; + byte nofterminalgroups, nofhelipadgroups; + + apc->size_x = size_x; + apc->size_y = size_y; + + /* Set up the terminal and helipad count for an airport. + * TODO: If there are more than 10 terminals or 4 helipads, internal variables + * need to be changed, so don't allow that for now */ + nofterminals = AirportGetTerminalCount(terminals, &nofterminalgroups); + if (nofterminals > MAX_TERMINALS) { + DEBUG(misc, 0, "[Ap] only a maximum of %d terminals are supported (requested %d)", MAX_TERMINALS, nofterminals); + assert(nofterminals <= MAX_TERMINALS); + } + apc->terminals = terminals; + + nofhelipads = AirportGetTerminalCount(helipads, &nofhelipadgroups); + if (nofhelipads > MAX_HELIPADS) { + DEBUG(misc, 0, "[Ap] only a maximum of %d helipads are supported (requested %d)", MAX_HELIPADS, nofhelipads); + assert(nofhelipads <= MAX_HELIPADS); + } + apc->helipads = helipads; + + /* Get the number of elements from the source table. We also double check this + * with the entry point which must be within bounds and use this information + * later on to build and validate the state machine */ + apc->nofelements = AirportGetNofElements(apFA); + if (entry_point >= apc->nofelements) { + DEBUG(misc, 0, "[Ap] entry (%d) must be within the airport (maximum %d)", entry_point, apc->nofelements); + assert(entry_point < apc->nofelements); + } + + apc->acc_planes = acc_planes; + apc->entry_point = entry_point; + apc->airport_depots = depots; + apc->nof_depots = nof_depots; + + /* Build the state machine itself */ + AirportBuildAutomata(apc, apFA); + DEBUG(misc, 2, "[Ap] #count %3d; #term %2d (%dgrp); #helipad %2d (%dgrp); entry %3d", + apc->nofelements, nofterminals, nofterminalgroups, nofhelipads, nofhelipadgroups, apc->entry_point); + + /* Test if everything went allright. This is only a rude static test checking + * the symantic correctness. By no means does passing the test mean that the + * airport is working correctly or will not deadlock for example */ + { byte ret = AirportTestFTA(apc); + if (ret != MAX_ELEMENTS) DEBUG(misc, 0, "[Ap] problem with element: %d", ret - 1); + assert(ret == MAX_ELEMENTS); + } + +#ifdef DEBUG_AIRPORT + AirportPrintOut(apc, DEBUG_AIRPORT); +#endif +} + +static void AirportFTAClass_Destructor(AirportFTAClass *apc) +{ + int i; + AirportFTA *current, *next; + + for (i = 0; i < apc->nofelements; i++) { + current = apc->layout[i].next; + while (current != NULL) { + next = current->next; + free(current); + current = next; + }; + } + free(apc->layout); + free(apc); +} + +/** Get the number of elements of a source Airport state automata + * Since it is actually just a big array of AirportFTA types, we only + * know one element from the other by differing 'position' identifiers */ +static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA) +{ + int i; + uint16 nofelements = 0; + int temp = apFA[0].position; + + for (i = 0; i < MAX_ELEMENTS; i++) { + if (temp != apFA[i].position) { + nofelements++; + temp = apFA[i].position; + } + if (apFA[i].position == MAX_ELEMENTS) break; + } + return nofelements; +} + +/* We calculate the terminal/helipod count based on the data passed to us + * This data (terminals) contains an index as a first element as to how many + * groups there are, and then the number of terminals for each group */ +static byte AirportGetTerminalCount(const byte *terminals, byte *groups) +{ + byte i; + byte nof_terminals = 0; + *groups = 0; + + if (terminals != NULL) { + i = terminals[0]; + *groups = i; + while (i-- > 0) { + terminals++; + assert(*terminals != 0); // no empty groups please + nof_terminals += *terminals; + } + } + return nof_terminals; +} + +static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA) +{ + AirportFTA *current; + AirportFTA *FAutomata = malloc(sizeof(AirportFTA) * apc->nofelements); + uint16 internalcounter = 0; + uint16 i; + + apc->layout = FAutomata; + for (i = 0; i < apc->nofelements; i++) { + current = &apc->layout[i]; + current->position = apFA[internalcounter].position; + current->heading = apFA[internalcounter].heading; + current->block = apFA[internalcounter].block; + current->next_position = apFA[internalcounter].next; + + // outgoing nodes from the same position, create linked list + while (current->position == apFA[internalcounter + 1].position) { + AirportFTA *newNode = malloc(sizeof(AirportFTA)); + + newNode->position = apFA[internalcounter + 1].position; + newNode->heading = apFA[internalcounter + 1].heading; + newNode->block = apFA[internalcounter + 1].block; + newNode->next_position = apFA[internalcounter + 1].next; + // create link + current->next = newNode; + current = current->next; + internalcounter++; + } // while + current->next = NULL; + internalcounter++; + } +} + +static byte AirportTestFTA(const AirportFTAClass *apc) +{ + byte position, i, next_position; + AirportFTA *current, *first; + next_position = 0; + + for (i = 0; i < apc->nofelements; i++) { + position = apc->layout[i].position; + if (position != next_position) return i; + current = first = &apc->layout[i]; + + for (; current != NULL; current = current->next) { + /* A heading must always be valid. The only exceptions are + * - multiple choices as start, identified by a special value of 255 + * - terminal group which is identified by a special value of 255 */ + if (current->heading > MAX_HEADINGS) { + if (current->heading != 255) return i; + if (current == first && current->next == NULL) return i; + if (current != first && current->next_position > apc->terminals[0]) return i; + } + + /* If there is only one choice, it must be at the end */ + if (current->heading == 0 && current->next != NULL) return i; + /* Obviously the elements of the linked list must have the same identifier */ + if (position != current->position) return i; + /* A next position must be within bounds */ + if (current->next_position >= apc->nofelements) return i; + } + next_position++; + } + return MAX_ELEMENTS; +} + +#ifdef DEBUG_AIRPORT +static const char* const _airport_heading_strings[] = { + "TO_ALL", + "HANGAR", + "TERM1", + "TERM2", + "TERM3", + "TERM4", + "TERM5", + "TERM6", + "HELIPAD1", + "HELIPAD2", + "TAKEOFF", + "STARTTAKEOFF", + "ENDTAKEOFF", + "HELITAKEOFF", + "FLYING", + "LANDING", + "ENDLANDING", + "HELILANDING", + "HELIENDLANDING", + "TERM7", + "TERM8", + "HELIPAD3", + "HELIPAD4", + "DUMMY" // extra heading for 255 +}; + +static uint AirportBlockToString(uint32 block) +{ + uint i = 0; + if (block & 0xffff0000) { block >>= 16; i += 16; } + if (block & 0x0000ff00) { block >>= 8; i += 8; } + if (block & 0x000000f0) { block >>= 4; i += 4; } + if (block & 0x0000000c) { block >>= 2; i += 2; } + if (block & 0x00000002) { i += 1; } + return i; +} + +static void AirportPrintOut(const AirportFTAClass *apc, bool full_report) +{ + uint16 i; + + if (!full_report) printf("(P = Current Position; NP = Next Position)\n"); + + for (i = 0; i < apc->nofelements; i++) { + AirportFTA *current = &apc->layout[i]; + + for (; current != NULL; current = current->next) { + if (full_report) { + byte heading = (current->heading == 255) ? MAX_HEADINGS + 1 : current->heading; + printf("\tPos:%2d NPos:%2d Heading:%15s Block:%2d\n", current->position, + current->next_position, _airport_heading_strings[heading], + AirportBlockToString(current->block)); + } else { + printf("P:%2d NP:%2d", current->position, current->next_position); + } + } + printf("\n"); + } +} +#endif + +const AirportFTAClass *GetAirport(const byte airport_type) +{ + //FIXME -- AircraftNextAirportPos_and_Order -> Needs something nicer, don't like this code + // needs constant change if more airports are added + switch (airport_type) { + default: NOT_REACHED(); + case AT_SMALL: return CountryAirport; + case AT_LARGE: return CityAirport; + case AT_METROPOLITAN: return MetropolitanAirport; + case AT_HELIPORT: return Heliport; + case AT_OILRIG: return Oilrig; + case AT_INTERNATIONAL: return InternationalAirport; + case AT_COMMUTER: return CommuterAirport; + case AT_HELIDEPOT: return HeliDepot; + case AT_INTERCON: return IntercontinentalAirport; + case AT_HELISTATION: return HeliStation; + } +} + +const AirportMovingData *GetAirportMovingData(byte airport_type, byte position) +{ + assert(airport_type < lengthof(_airport_moving_datas)); + assert(position < GetAirport(airport_type)->nofelements); + return &_airport_moving_datas[airport_type][position]; +} + +uint32 GetValidAirports(void) +{ + uint32 bytemask = _avail_aircraft; /// sets the first 3 bytes, 0 - 2, @see AdjustAvailAircraft() + + if (_cur_year >= 1980) SETBIT(bytemask, 3); // metropolitan airport + if (_cur_year >= 1990) SETBIT(bytemask, 4); // international airport + if (_cur_year >= 1983) SETBIT(bytemask, 5); // commuter airport + if (_cur_year >= 1976) SETBIT(bytemask, 6); // helidepot + if (_cur_year >= 2002) SETBIT(bytemask, 7); // intercontinental airport + if (_cur_year >= 1980) SETBIT(bytemask, 8); // helistation + return bytemask; +} diff --git a/src/airport_gui.c b/src/airport_gui.c deleted file mode 100644 --- a/src/airport_gui.c +++ /dev/null @@ -1,286 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "table/sprites.h" -#include "table/strings.h" -#include "functions.h" -#include "map.h" -#include "window.h" -#include "gui.h" -#include "viewport.h" -#include "gfx.h" -#include "sound.h" -#include "command.h" -#include "vehicle.h" -#include "station.h" -#include "airport.h" -#include "depot.h" - -static byte _selected_airport_type; - -static void ShowBuildAirportPicker(void); - - -void CcBuildAirport(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - if (success) { - SndPlayTileFx(SND_1F_SPLAT, tile); - ResetObjectToPlace(); - } -} - -static void PlaceAirport(TileIndex tile) -{ - DoCommandP(tile, _selected_airport_type, 0, CcBuildAirport, CMD_BUILD_AIRPORT | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_A001_CAN_T_BUILD_AIRPORT_HERE)); -} - -static void PlaceAir_DemolishArea(TileIndex tile) -{ - VpStartPlaceSizing(tile, 4); -} - - -enum { - ATW_AIRPORT = 3, - ATW_DEMOLISH = 4 -}; - - -static void BuildAirClick_Airport(Window *w) -{ - if (HandlePlacePushButton(w, ATW_AIRPORT, SPR_CURSOR_AIRPORT, 1, PlaceAirport)) ShowBuildAirportPicker(); -} - -static void BuildAirClick_Demolish(Window *w) -{ - HandlePlacePushButton(w, ATW_DEMOLISH, ANIMCURSOR_DEMOLISH, 1, PlaceAir_DemolishArea); -} - -static void BuildAirClick_Landscaping(Window *w) -{ - ShowTerraformToolbar(); -} - -typedef void OnButtonClick(Window *w); -static OnButtonClick * const _build_air_button_proc[] = { - BuildAirClick_Airport, - BuildAirClick_Demolish, - BuildAirClick_Landscaping, -}; - -static void BuildAirToolbWndProc(Window *w, WindowEvent *e) -{ - switch (e->event) { - case WE_PAINT: - DrawWindowWidgets(w); - break; - - case WE_CLICK: - if (e->we.click.widget - 3 >= 0) - _build_air_button_proc[e->we.click.widget - 3](w); - break; - - case WE_KEYPRESS: { - switch (e->we.keypress.keycode) { - case '1': BuildAirClick_Airport(w); break; - case '2': BuildAirClick_Demolish(w); break; - case 'l': BuildAirClick_Landscaping(w); break; - default: return; - } - } break; - - case WE_PLACE_OBJ: - _place_proc(e->we.place.tile); - break; - - case WE_PLACE_DRAG: - VpSelectTilesWithMethod(e->we.place.pt.x, e->we.place.pt.y, e->we.place.userdata); - break; - - case WE_PLACE_MOUSEUP: - if (e->we.place.pt.x != -1) { - DoCommandP(e->we.place.tile, e->we.place.starttile, 0, CcPlaySound10, CMD_CLEAR_AREA | CMD_MSG(STR_00B5_CAN_T_CLEAR_THIS_AREA)); - } - break; - - case WE_ABORT_PLACE_OBJ: - RaiseWindowButtons(w); - - w = FindWindowById(WC_BUILD_STATION, 0); - if (w != 0) - WP(w,def_d).close = true; - break; - - case WE_DESTROY: - if (_patches.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0); - break; - } -} - -static const Widget _air_toolbar_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, -{ WWT_CAPTION, RESIZE_NONE, 7, 11, 73, 0, 13, STR_A000_AIRPORTS, STR_018C_WINDOW_TITLE_DRAG_THIS }, -{ WWT_STICKYBOX, RESIZE_NONE, 7, 74, 85, 0, 13, 0x0, STR_STICKY_BUTTON }, -{ WWT_IMGBTN, RESIZE_NONE, 7, 0, 41, 14, 35, SPR_IMG_AIRPORT, STR_A01E_BUILD_AIRPORT }, -{ WWT_IMGBTN, RESIZE_NONE, 7, 42, 63, 14, 35, SPR_IMG_DYNAMITE, STR_018D_DEMOLISH_BUILDINGS_ETC }, -{ WWT_IMGBTN, RESIZE_NONE, 7, 64, 85, 14, 35, SPR_IMG_LANDSCAPING, STR_LANDSCAPING_TOOLBAR_TIP }, -{ WIDGETS_END}, -}; - - -static const WindowDesc _air_toolbar_desc = { - WDP_ALIGN_TBR, 22, 86, 36, - WC_BUILD_TOOLBAR, 0, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON, - _air_toolbar_widgets, - BuildAirToolbWndProc -}; - -void ShowBuildAirToolbar(void) -{ - if (!IsValidPlayer(_current_player)) return; - - DeleteWindowById(WC_BUILD_TOOLBAR, 0); - AllocateWindowDescFront(&_air_toolbar_desc, 0); - if (_patches.link_terraform_toolbar) ShowTerraformToolbar(); -} - -static void BuildAirportPickerWndProc(Window *w, WindowEvent *e) -{ - switch (e->event) { - case WE_CREATE: - SetWindowWidgetLoweredState(w, 16, !_station_show_coverage); - SetWindowWidgetLoweredState(w, 17, _station_show_coverage); - LowerWindowWidget(w, _selected_airport_type + 7); - break; - - case WE_PAINT: { - int i; // airport enabling loop - int rad = 4; // default catchment radious - uint32 avail_airports; - const AirportFTAClass *airport; - - if (WP(w,def_d).close) return; - - avail_airports = GetValidAirports(); - - RaiseWindowWidget(w, _selected_airport_type + 7); - if (!HASBIT(avail_airports, 0) && _selected_airport_type == AT_SMALL) _selected_airport_type = AT_LARGE; - if (!HASBIT(avail_airports, 1) && _selected_airport_type == AT_LARGE) _selected_airport_type = AT_SMALL; - LowerWindowWidget(w, _selected_airport_type + 7); - - /* 'Country Airport' starts at widget 7, and if its bit is set, it is - * available, so take its opposite value to set the disabled state. - * There are 9 buildable airports - * XXX TODO : all airports should be held in arrays, with all relevant data. - * This should be part of newgrf-airports, i suppose - */ - for (i = 0; i < 9; i++) SetWindowWidgetDisabledState(w, i + 7, !HASBIT(avail_airports, i)); - - // select default the coverage area to 'Off' (16) - airport = GetAirport(_selected_airport_type); - SetTileSelectSize(airport->size_x, airport->size_y); - - if (_patches.modified_catchment) { - switch (_selected_airport_type) { - case AT_OILRIG: rad = CA_AIR_OILPAD; break; - case AT_HELIPORT: rad = CA_AIR_HELIPORT; break; - case AT_SMALL: rad = CA_AIR_SMALL; break; - case AT_LARGE: rad = CA_AIR_LARGE; break; - case AT_METROPOLITAN: rad = CA_AIR_METRO; break; - case AT_INTERNATIONAL: rad = CA_AIR_INTER; break; - case AT_COMMUTER: rad = CA_AIR_COMMUTER; break; - case AT_HELIDEPOT: rad = CA_AIR_HELIDEPOT; break; - case AT_INTERCON: rad = CA_AIR_INTERCON; break; - case AT_HELISTATION: rad = CA_AIR_HELISTATION; break; - } - } - - if (_station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad); - - DrawWindowWidgets(w); - // strings such as 'Size' and 'Coverage Area' - // 'Coverage Area' - DrawStationCoverageAreaText(2, 206, (uint)-1, rad); - break; - } - - case WE_CLICK: { - switch (e->we.click.widget) { - case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: - RaiseWindowWidget(w, _selected_airport_type + 7); - _selected_airport_type = e->we.click.widget - 7; - LowerWindowWidget(w, _selected_airport_type + 7); - SndPlayFx(SND_15_BEEP); - SetWindowDirty(w); - break; - case 16: case 17: - _station_show_coverage = e->we.click.widget - 16; - SetWindowWidgetLoweredState(w, 16, !_station_show_coverage); - SetWindowWidgetLoweredState(w, 17, _station_show_coverage); - SndPlayFx(SND_15_BEEP); - SetWindowDirty(w); - break; - } - } break; - - case WE_MOUSELOOP: { - if (WP(w,def_d).close) { - DeleteWindow(w); - return; - } - - CheckRedrawStationCoverage(w); - } break; - - case WE_DESTROY: - if (!WP(w,def_d).close) ResetObjectToPlace(); - break; - } -} - -static const Widget _build_airport_picker_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 7, 11, 147, 0, 13, STR_3001_AIRPORT_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 14, 52, 0x0, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 53, 89, 0x0, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 90, 127, 0x0, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 128, 177, 0x0, STR_NULL}, -{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 178, 239, 0x0, STR_NULL}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 27, 38, STR_SMALL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 65, 76, STR_CITY_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 141, 152, STR_HELIPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 77, 88, STR_METRO_AIRPORT , STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 103, 114, STR_INTERNATIONAL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 39, 50, STR_COMMUTER_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 165, 176, STR_HELIDEPOT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 115, 126, STR_INTERCONTINENTAL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 153, 164, STR_HELISTATION, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 14, 73, 191, 202, STR_02DB_OFF, STR_3065_DON_T_HIGHLIGHT_COVERAGE}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 74, 133, 191, 202, STR_02DA_ON, STR_3064_HIGHLIGHT_COVERAGE_AREA}, -{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 14, 27, STR_SMALL_AIRPORTS, STR_NULL}, -{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 52, 65, STR_LARGE_AIRPORTS, STR_NULL}, -{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 90, 103, STR_HUB_AIRPORTS, STR_NULL}, -{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 128, 141, STR_HELIPORTS, STR_NULL}, -{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 178, 191, STR_3066_COVERAGE_AREA_HIGHLIGHT, STR_NULL}, -{ WIDGETS_END}, -}; - -static const WindowDesc _build_airport_desc = { - WDP_AUTO, WDP_AUTO, 148, 240, - WC_BUILD_STATION, WC_BUILD_TOOLBAR, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, - _build_airport_picker_widgets, - BuildAirportPickerWndProc -}; - -static void ShowBuildAirportPicker(void) -{ - AllocateWindowDesc(&_build_airport_desc); -} - -void InitializeAirportGui(void) -{ - _selected_airport_type = AT_SMALL; -} diff --git a/src/airport_gui.cpp b/src/airport_gui.cpp new file mode 100644 --- /dev/null +++ b/src/airport_gui.cpp @@ -0,0 +1,286 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "table/sprites.h" +#include "table/strings.h" +#include "functions.h" +#include "map.h" +#include "window.h" +#include "gui.h" +#include "viewport.h" +#include "gfx.h" +#include "sound.h" +#include "command.h" +#include "vehicle.h" +#include "station.h" +#include "airport.h" +#include "depot.h" + +static byte _selected_airport_type; + +static void ShowBuildAirportPicker(void); + + +void CcBuildAirport(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + if (success) { + SndPlayTileFx(SND_1F_SPLAT, tile); + ResetObjectToPlace(); + } +} + +static void PlaceAirport(TileIndex tile) +{ + DoCommandP(tile, _selected_airport_type, 0, CcBuildAirport, CMD_BUILD_AIRPORT | CMD_AUTO | CMD_NO_WATER | CMD_MSG(STR_A001_CAN_T_BUILD_AIRPORT_HERE)); +} + +static void PlaceAir_DemolishArea(TileIndex tile) +{ + VpStartPlaceSizing(tile, 4); +} + + +enum { + ATW_AIRPORT = 3, + ATW_DEMOLISH = 4 +}; + + +static void BuildAirClick_Airport(Window *w) +{ + if (HandlePlacePushButton(w, ATW_AIRPORT, SPR_CURSOR_AIRPORT, 1, PlaceAirport)) ShowBuildAirportPicker(); +} + +static void BuildAirClick_Demolish(Window *w) +{ + HandlePlacePushButton(w, ATW_DEMOLISH, ANIMCURSOR_DEMOLISH, 1, PlaceAir_DemolishArea); +} + +static void BuildAirClick_Landscaping(Window *w) +{ + ShowTerraformToolbar(); +} + +typedef void OnButtonClick(Window *w); +static OnButtonClick * const _build_air_button_proc[] = { + BuildAirClick_Airport, + BuildAirClick_Demolish, + BuildAirClick_Landscaping, +}; + +static void BuildAirToolbWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: + DrawWindowWidgets(w); + break; + + case WE_CLICK: + if (e->we.click.widget - 3 >= 0) + _build_air_button_proc[e->we.click.widget - 3](w); + break; + + case WE_KEYPRESS: { + switch (e->we.keypress.keycode) { + case '1': BuildAirClick_Airport(w); break; + case '2': BuildAirClick_Demolish(w); break; + case 'l': BuildAirClick_Landscaping(w); break; + default: return; + } + } break; + + case WE_PLACE_OBJ: + _place_proc(e->we.place.tile); + break; + + case WE_PLACE_DRAG: + VpSelectTilesWithMethod(e->we.place.pt.x, e->we.place.pt.y, e->we.place.userdata); + break; + + case WE_PLACE_MOUSEUP: + if (e->we.place.pt.x != -1) { + DoCommandP(e->we.place.tile, e->we.place.starttile, 0, CcPlaySound10, CMD_CLEAR_AREA | CMD_MSG(STR_00B5_CAN_T_CLEAR_THIS_AREA)); + } + break; + + case WE_ABORT_PLACE_OBJ: + RaiseWindowButtons(w); + + w = FindWindowById(WC_BUILD_STATION, 0); + if (w != 0) + WP(w,def_d).close = true; + break; + + case WE_DESTROY: + if (_patches.link_terraform_toolbar) DeleteWindowById(WC_SCEN_LAND_GEN, 0); + break; + } +} + +static const Widget _air_toolbar_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 73, 0, 13, STR_A000_AIRPORTS, STR_018C_WINDOW_TITLE_DRAG_THIS }, +{ WWT_STICKYBOX, RESIZE_NONE, 7, 74, 85, 0, 13, 0x0, STR_STICKY_BUTTON }, +{ WWT_IMGBTN, RESIZE_NONE, 7, 0, 41, 14, 35, SPR_IMG_AIRPORT, STR_A01E_BUILD_AIRPORT }, +{ WWT_IMGBTN, RESIZE_NONE, 7, 42, 63, 14, 35, SPR_IMG_DYNAMITE, STR_018D_DEMOLISH_BUILDINGS_ETC }, +{ WWT_IMGBTN, RESIZE_NONE, 7, 64, 85, 14, 35, SPR_IMG_LANDSCAPING, STR_LANDSCAPING_TOOLBAR_TIP }, +{ WIDGETS_END}, +}; + + +static const WindowDesc _air_toolbar_desc = { + WDP_ALIGN_TBR, 22, 86, 36, + WC_BUILD_TOOLBAR, 0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_STICKY_BUTTON, + _air_toolbar_widgets, + BuildAirToolbWndProc +}; + +void ShowBuildAirToolbar(void) +{ + if (!IsValidPlayer(_current_player)) return; + + DeleteWindowById(WC_BUILD_TOOLBAR, 0); + AllocateWindowDescFront(&_air_toolbar_desc, 0); + if (_patches.link_terraform_toolbar) ShowTerraformToolbar(); +} + +static void BuildAirportPickerWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_CREATE: + SetWindowWidgetLoweredState(w, 16, !_station_show_coverage); + SetWindowWidgetLoweredState(w, 17, _station_show_coverage); + LowerWindowWidget(w, _selected_airport_type + 7); + break; + + case WE_PAINT: { + int i; // airport enabling loop + int rad = 4; // default catchment radious + uint32 avail_airports; + const AirportFTAClass *airport; + + if (WP(w,def_d).close) return; + + avail_airports = GetValidAirports(); + + RaiseWindowWidget(w, _selected_airport_type + 7); + if (!HASBIT(avail_airports, 0) && _selected_airport_type == AT_SMALL) _selected_airport_type = AT_LARGE; + if (!HASBIT(avail_airports, 1) && _selected_airport_type == AT_LARGE) _selected_airport_type = AT_SMALL; + LowerWindowWidget(w, _selected_airport_type + 7); + + /* 'Country Airport' starts at widget 7, and if its bit is set, it is + * available, so take its opposite value to set the disabled state. + * There are 9 buildable airports + * XXX TODO : all airports should be held in arrays, with all relevant data. + * This should be part of newgrf-airports, i suppose + */ + for (i = 0; i < 9; i++) SetWindowWidgetDisabledState(w, i + 7, !HASBIT(avail_airports, i)); + + // select default the coverage area to 'Off' (16) + airport = GetAirport(_selected_airport_type); + SetTileSelectSize(airport->size_x, airport->size_y); + + if (_patches.modified_catchment) { + switch (_selected_airport_type) { + case AT_OILRIG: rad = CA_AIR_OILPAD; break; + case AT_HELIPORT: rad = CA_AIR_HELIPORT; break; + case AT_SMALL: rad = CA_AIR_SMALL; break; + case AT_LARGE: rad = CA_AIR_LARGE; break; + case AT_METROPOLITAN: rad = CA_AIR_METRO; break; + case AT_INTERNATIONAL: rad = CA_AIR_INTER; break; + case AT_COMMUTER: rad = CA_AIR_COMMUTER; break; + case AT_HELIDEPOT: rad = CA_AIR_HELIDEPOT; break; + case AT_INTERCON: rad = CA_AIR_INTERCON; break; + case AT_HELISTATION: rad = CA_AIR_HELISTATION; break; + } + } + + if (_station_show_coverage) SetTileSelectBigSize(-rad, -rad, 2 * rad, 2 * rad); + + DrawWindowWidgets(w); + // strings such as 'Size' and 'Coverage Area' + // 'Coverage Area' + DrawStationCoverageAreaText(2, 206, (uint)-1, rad); + break; + } + + case WE_CLICK: { + switch (e->we.click.widget) { + case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: + RaiseWindowWidget(w, _selected_airport_type + 7); + _selected_airport_type = e->we.click.widget - 7; + LowerWindowWidget(w, _selected_airport_type + 7); + SndPlayFx(SND_15_BEEP); + SetWindowDirty(w); + break; + case 16: case 17: + _station_show_coverage = e->we.click.widget - 16; + SetWindowWidgetLoweredState(w, 16, !_station_show_coverage); + SetWindowWidgetLoweredState(w, 17, _station_show_coverage); + SndPlayFx(SND_15_BEEP); + SetWindowDirty(w); + break; + } + } break; + + case WE_MOUSELOOP: { + if (WP(w,def_d).close) { + DeleteWindow(w); + return; + } + + CheckRedrawStationCoverage(w); + } break; + + case WE_DESTROY: + if (!WP(w,def_d).close) ResetObjectToPlace(); + break; + } +} + +static const Widget _build_airport_picker_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 147, 0, 13, STR_3001_AIRPORT_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 14, 52, 0x0, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 53, 89, 0x0, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 90, 127, 0x0, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 128, 177, 0x0, STR_NULL}, +{ WWT_PANEL, RESIZE_NONE, 7, 0, 147, 178, 239, 0x0, STR_NULL}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 27, 38, STR_SMALL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 65, 76, STR_CITY_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 141, 152, STR_HELIPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 77, 88, STR_METRO_AIRPORT , STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 103, 114, STR_INTERNATIONAL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 39, 50, STR_COMMUTER_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 165, 176, STR_HELIDEPOT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 115, 126, STR_INTERCONTINENTAL_AIRPORT, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 2, 145, 153, 164, STR_HELISTATION, STR_3058_SELECT_SIZE_TYPE_OF_AIRPORT}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 14, 73, 191, 202, STR_02DB_OFF, STR_3065_DON_T_HIGHLIGHT_COVERAGE}, +{ WWT_TEXTBTN, RESIZE_NONE, 14, 74, 133, 191, 202, STR_02DA_ON, STR_3064_HIGHLIGHT_COVERAGE_AREA}, +{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 14, 27, STR_SMALL_AIRPORTS, STR_NULL}, +{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 52, 65, STR_LARGE_AIRPORTS, STR_NULL}, +{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 90, 103, STR_HUB_AIRPORTS, STR_NULL}, +{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 128, 141, STR_HELIPORTS, STR_NULL}, +{ WWT_LABEL, RESIZE_NONE, 7, 0, 147, 178, 191, STR_3066_COVERAGE_AREA_HIGHLIGHT, STR_NULL}, +{ WIDGETS_END}, +}; + +static const WindowDesc _build_airport_desc = { + WDP_AUTO, WDP_AUTO, 148, 240, + WC_BUILD_STATION, WC_BUILD_TOOLBAR, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _build_airport_picker_widgets, + BuildAirportPickerWndProc +}; + +static void ShowBuildAirportPicker(void) +{ + AllocateWindowDesc(&_build_airport_desc); +} + +void InitializeAirportGui(void) +{ + _selected_airport_type = AT_SMALL; +} diff --git a/src/aystar.c b/src/aystar.c deleted file mode 100644 --- a/src/aystar.c +++ /dev/null @@ -1,296 +0,0 @@ -/* $Id$ */ - -/* - * This file has the core function for AyStar - * AyStar is a fast pathfinding routine and is used for things like - * AI_pathfinding and Train_pathfinding. - * For more information about AyStar (A* Algorithm), you can look at - * http://en.wikipedia.org/wiki/A-star_search_algorithm - */ - -/* - * Friendly reminder: - * Call (AyStar).free() when you are done with Aystar. It reserves a lot of memory - * And when not free'd, it can cause system-crashes. - * Also remember that when you stop an algorithm before it is finished, your - * should call clear() yourself! - */ - -#include "stdafx.h" -#include "openttd.h" -#include "aystar.h" - -int _aystar_stats_open_size; -int _aystar_stats_closed_size; - -// This looks in the Hash if a node exists in ClosedList -// If so, it returns the PathNode, else NULL -static PathNode* AyStarMain_ClosedList_IsInList(AyStar *aystar, const AyStarNode *node) -{ - return (PathNode*)Hash_Get(&aystar->ClosedListHash, node->tile, node->direction); -} - -// This adds a node to the ClosedList -// It makes a copy of the data -static void AyStarMain_ClosedList_Add(AyStar *aystar, const PathNode *node) -{ - // Add a node to the ClosedList - PathNode *new_node = malloc(sizeof(*new_node)); - *new_node = *node; - Hash_Set(&aystar->ClosedListHash, node->node.tile, node->node.direction, new_node); -} - -// Checks if a node is in the OpenList -// If so, it returns the OpenListNode, else NULL -static OpenListNode *AyStarMain_OpenList_IsInList(AyStar *aystar, const AyStarNode *node) -{ - return (OpenListNode*)Hash_Get(&aystar->OpenListHash, node->tile, node->direction); -} - -// Gets the best node from OpenList -// returns the best node, or NULL of none is found -// Also it deletes the node from the OpenList -static OpenListNode *AyStarMain_OpenList_Pop(AyStar *aystar) -{ - // Return the item the Queue returns.. the best next OpenList item. - OpenListNode *res = (OpenListNode*)aystar->OpenListQueue.pop(&aystar->OpenListQueue); - if (res != NULL) { - Hash_Delete(&aystar->OpenListHash, res->path.node.tile, res->path.node.direction); - } - - return res; -} - -// Adds a node to the OpenList -// It makes a copy of node, and puts the pointer of parent in the struct -static void AyStarMain_OpenList_Add(AyStar *aystar, PathNode *parent, const AyStarNode *node, int f, int g) -{ - // Add a new Node to the OpenList - OpenListNode *new_node = malloc(sizeof(*new_node)); - new_node->g = g; - new_node->path.parent = parent; - new_node->path.node = *node; - Hash_Set(&aystar->OpenListHash, node->tile, node->direction, new_node); - - // Add it to the queue - aystar->OpenListQueue.push(&aystar->OpenListQueue, new_node, f); -} - -/* - * Checks one tile and calculate his f-value - * return values: - * AYSTAR_DONE : indicates we are done - */ -int AyStarMain_CheckTile(AyStar *aystar, AyStarNode *current, OpenListNode *parent) -{ - int new_f, new_g, new_h; - PathNode *closedlist_parent; - OpenListNode *check; - - // Check the new node against the ClosedList - if (AyStarMain_ClosedList_IsInList(aystar, current) != NULL) return AYSTAR_DONE; - - // Calculate the G-value for this node - new_g = aystar->CalculateG(aystar, current, parent); - // If the value was INVALID_NODE, we don't do anything with this node - if (new_g == AYSTAR_INVALID_NODE) return AYSTAR_DONE; - - // There should not be given any other error-code.. - assert(new_g >= 0); - // Add the parent g-value to the new g-value - new_g += parent->g; - if (aystar->max_path_cost != 0 && (uint)new_g > aystar->max_path_cost) return AYSTAR_DONE; - - // Calculate the h-value - new_h = aystar->CalculateH(aystar, current, parent); - // There should not be given any error-code.. - assert(new_h >= 0); - - // The f-value if g + h - new_f = new_g + new_h; - - // Get the pointer to the parent in the ClosedList (the currentone is to a copy of the one in the OpenList) - closedlist_parent = AyStarMain_ClosedList_IsInList(aystar, &parent->path.node); - - // Check if this item is already in the OpenList - check = AyStarMain_OpenList_IsInList(aystar, current); - if (check != NULL) { - uint i; - // Yes, check if this g value is lower.. - if (new_g > check->g) return AYSTAR_DONE; - aystar->OpenListQueue.del(&aystar->OpenListQueue, check, 0); - // It is lower, so change it to this item - check->g = new_g; - check->path.parent = closedlist_parent; - /* Copy user data, will probably have changed */ - for (i = 0; i < lengthof(current->user_data); i++) { - check->path.node.user_data[i] = current->user_data[i]; - } - // Readd him in the OpenListQueue - aystar->OpenListQueue.push(&aystar->OpenListQueue, check, new_f); - } else { - // A new node, add him to the OpenList - AyStarMain_OpenList_Add(aystar, closedlist_parent, current, new_f, new_g); - } - - return AYSTAR_DONE; -} - -/* - * This function is the core of AyStar. It handles one item and checks - * his neighbour items. If they are valid, they are added to be checked too. - * return values: - * AYSTAR_EMPTY_OPENLIST : indicates all items are tested, and no path - * has been found. - * AYSTAR_LIMIT_REACHED : Indicates that the max_nodes limit has been - * reached. - * AYSTAR_FOUND_END_NODE : indicates we found the end. Path_found now is true, and in path is the path found. - * AYSTAR_STILL_BUSY : indicates we have done this tile, did not found the path yet, and have items left to try. - */ -int AyStarMain_Loop(AyStar *aystar) -{ - int i, r; - - // Get the best node from OpenList - OpenListNode *current = AyStarMain_OpenList_Pop(aystar); - // If empty, drop an error - if (current == NULL) return AYSTAR_EMPTY_OPENLIST; - - // Check for end node and if found, return that code - if (aystar->EndNodeCheck(aystar, current) == AYSTAR_FOUND_END_NODE) { - if (aystar->FoundEndNode != NULL) - aystar->FoundEndNode(aystar, current); - free(current); - return AYSTAR_FOUND_END_NODE; - } - - // Add the node to the ClosedList - AyStarMain_ClosedList_Add(aystar, ¤t->path); - - // Load the neighbours - aystar->GetNeighbours(aystar, current); - - // Go through all neighbours - for (i = 0; i < aystar->num_neighbours; i++) { - // Check and add them to the OpenList if needed - r = aystar->checktile(aystar, &aystar->neighbours[i], current); - } - - // Free the node - free(current); - - if (aystar->max_search_nodes != 0 && Hash_Size(&aystar->ClosedListHash) >= aystar->max_search_nodes) { - /* We've expanded enough nodes */ - return AYSTAR_LIMIT_REACHED; - } else { - // Return that we are still busy - return AYSTAR_STILL_BUSY; - } -} - -/* - * This function frees the memory it allocated - */ -void AyStarMain_Free(AyStar *aystar) -{ - aystar->OpenListQueue.free(&aystar->OpenListQueue, false); - /* 2nd argument above is false, below is true, to free the values only - * once */ - delete_Hash(&aystar->OpenListHash, true); - delete_Hash(&aystar->ClosedListHash, true); -#ifdef AYSTAR_DEBUG - printf("[AyStar] Memory free'd\n"); -#endif -} - -/* - * This function make the memory go back to zero - * This function should be called when you are using the same instance again. - */ -void AyStarMain_Clear(AyStar *aystar) -{ - // Clean the Queue, but not the elements within. That will be done by - // the hash. - aystar->OpenListQueue.clear(&aystar->OpenListQueue, false); - // Clean the hashes - clear_Hash(&aystar->OpenListHash, true); - clear_Hash(&aystar->ClosedListHash, true); - -#ifdef AYSTAR_DEBUG - printf("[AyStar] Cleared AyStar\n"); -#endif -} - -/* - * This is the function you call to run AyStar. - * return values: - * AYSTAR_FOUND_END_NODE : indicates we found an end node. - * AYSTAR_NO_PATH : indicates that there was no path found. - * AYSTAR_STILL_BUSY : indicates we have done some checked, that we did not found the path yet, and that we still have items left to try. - * When the algorithm is done (when the return value is not AYSTAR_STILL_BUSY) - * aystar->clear() is called. Note that when you stop the algorithm halfway, - * you should still call clear() yourself! - */ -int AyStarMain_Main(AyStar *aystar) { - int r, i = 0; - // Loop through the OpenList - // Quit if result is no AYSTAR_STILL_BUSY or is more than loops_per_tick - while ((r = aystar->loop(aystar)) == AYSTAR_STILL_BUSY && (aystar->loops_per_tick == 0 || ++i < aystar->loops_per_tick)) { } -#ifdef AYSTAR_DEBUG - switch (r) { - case AYSTAR_FOUND_END_NODE: printf("[AyStar] Found path!\n"); break; - case AYSTAR_EMPTY_OPENLIST: printf("[AyStar] OpenList run dry, no path found\n"); break; - case AYSTAR_LIMIT_REACHED: printf("[AyStar] Exceeded search_nodes, no path found\n"); break; - default: break; - } -#endif - if (r != AYSTAR_STILL_BUSY) { - /* We're done, clean up */ - _aystar_stats_open_size = aystar->OpenListHash.size; - _aystar_stats_closed_size = aystar->ClosedListHash.size; - aystar->clear(aystar); - } - - switch (r) { - case AYSTAR_FOUND_END_NODE: return AYSTAR_FOUND_END_NODE; - case AYSTAR_EMPTY_OPENLIST: - case AYSTAR_LIMIT_REACHED: return AYSTAR_NO_PATH; - default: return AYSTAR_STILL_BUSY; - } -} - -/* - * Adds a node from where to start an algorithm. Multiple nodes can be added - * if wanted. You should make sure that clear() is called before adding nodes - * if the AyStar has been used before (though the normal main loop calls - * clear() automatically when the algorithm finishes - * g is the cost for starting with this node. - */ -void AyStarMain_AddStartNode(AyStar *aystar, AyStarNode *start_node, uint g) -{ -#ifdef AYSTAR_DEBUG - printf("[AyStar] Starting A* Algorithm from node (%d, %d, %d)\n", - TileX(start_node->tile), TileY(start_node->tile), start_node->direction); -#endif - AyStarMain_OpenList_Add(aystar, NULL, start_node, 0, g); -} - -void init_AyStar(AyStar *aystar, Hash_HashProc hash, uint num_buckets) -{ - // Allocated the Hash for the OpenList and ClosedList - init_Hash(&aystar->OpenListHash, hash, num_buckets); - init_Hash(&aystar->ClosedListHash, hash, num_buckets); - - // Set up our sorting queue - // BinaryHeap allocates a block of 1024 nodes - // When thatone gets full it reserves an otherone, till this number - // That is why it can stay this high - init_BinaryHeap(&aystar->OpenListQueue, 102400); - - aystar->addstart = AyStarMain_AddStartNode; - aystar->main = AyStarMain_Main; - aystar->loop = AyStarMain_Loop; - aystar->free = AyStarMain_Free; - aystar->clear = AyStarMain_Clear; - aystar->checktile = AyStarMain_CheckTile; -} diff --git a/src/aystar.cpp b/src/aystar.cpp new file mode 100644 --- /dev/null +++ b/src/aystar.cpp @@ -0,0 +1,296 @@ +/* $Id$ */ + +/* + * This file has the core function for AyStar + * AyStar is a fast pathfinding routine and is used for things like + * AI_pathfinding and Train_pathfinding. + * For more information about AyStar (A* Algorithm), you can look at + * http://en.wikipedia.org/wiki/A-star_search_algorithm + */ + +/* + * Friendly reminder: + * Call (AyStar).free() when you are done with Aystar. It reserves a lot of memory + * And when not free'd, it can cause system-crashes. + * Also remember that when you stop an algorithm before it is finished, your + * should call clear() yourself! + */ + +#include "stdafx.h" +#include "openttd.h" +#include "aystar.h" + +int _aystar_stats_open_size; +int _aystar_stats_closed_size; + +// This looks in the Hash if a node exists in ClosedList +// If so, it returns the PathNode, else NULL +static PathNode* AyStarMain_ClosedList_IsInList(AyStar *aystar, const AyStarNode *node) +{ + return (PathNode*)Hash_Get(&aystar->ClosedListHash, node->tile, node->direction); +} + +// This adds a node to the ClosedList +// It makes a copy of the data +static void AyStarMain_ClosedList_Add(AyStar *aystar, const PathNode *node) +{ + // Add a node to the ClosedList + PathNode *new_node = malloc(sizeof(*new_node)); + *new_node = *node; + Hash_Set(&aystar->ClosedListHash, node->node.tile, node->node.direction, new_node); +} + +// Checks if a node is in the OpenList +// If so, it returns the OpenListNode, else NULL +static OpenListNode *AyStarMain_OpenList_IsInList(AyStar *aystar, const AyStarNode *node) +{ + return (OpenListNode*)Hash_Get(&aystar->OpenListHash, node->tile, node->direction); +} + +// Gets the best node from OpenList +// returns the best node, or NULL of none is found +// Also it deletes the node from the OpenList +static OpenListNode *AyStarMain_OpenList_Pop(AyStar *aystar) +{ + // Return the item the Queue returns.. the best next OpenList item. + OpenListNode *res = (OpenListNode*)aystar->OpenListQueue.pop(&aystar->OpenListQueue); + if (res != NULL) { + Hash_Delete(&aystar->OpenListHash, res->path.node.tile, res->path.node.direction); + } + + return res; +} + +// Adds a node to the OpenList +// It makes a copy of node, and puts the pointer of parent in the struct +static void AyStarMain_OpenList_Add(AyStar *aystar, PathNode *parent, const AyStarNode *node, int f, int g) +{ + // Add a new Node to the OpenList + OpenListNode *new_node = malloc(sizeof(*new_node)); + new_node->g = g; + new_node->path.parent = parent; + new_node->path.node = *node; + Hash_Set(&aystar->OpenListHash, node->tile, node->direction, new_node); + + // Add it to the queue + aystar->OpenListQueue.push(&aystar->OpenListQueue, new_node, f); +} + +/* + * Checks one tile and calculate his f-value + * return values: + * AYSTAR_DONE : indicates we are done + */ +int AyStarMain_CheckTile(AyStar *aystar, AyStarNode *current, OpenListNode *parent) +{ + int new_f, new_g, new_h; + PathNode *closedlist_parent; + OpenListNode *check; + + // Check the new node against the ClosedList + if (AyStarMain_ClosedList_IsInList(aystar, current) != NULL) return AYSTAR_DONE; + + // Calculate the G-value for this node + new_g = aystar->CalculateG(aystar, current, parent); + // If the value was INVALID_NODE, we don't do anything with this node + if (new_g == AYSTAR_INVALID_NODE) return AYSTAR_DONE; + + // There should not be given any other error-code.. + assert(new_g >= 0); + // Add the parent g-value to the new g-value + new_g += parent->g; + if (aystar->max_path_cost != 0 && (uint)new_g > aystar->max_path_cost) return AYSTAR_DONE; + + // Calculate the h-value + new_h = aystar->CalculateH(aystar, current, parent); + // There should not be given any error-code.. + assert(new_h >= 0); + + // The f-value if g + h + new_f = new_g + new_h; + + // Get the pointer to the parent in the ClosedList (the currentone is to a copy of the one in the OpenList) + closedlist_parent = AyStarMain_ClosedList_IsInList(aystar, &parent->path.node); + + // Check if this item is already in the OpenList + check = AyStarMain_OpenList_IsInList(aystar, current); + if (check != NULL) { + uint i; + // Yes, check if this g value is lower.. + if (new_g > check->g) return AYSTAR_DONE; + aystar->OpenListQueue.del(&aystar->OpenListQueue, check, 0); + // It is lower, so change it to this item + check->g = new_g; + check->path.parent = closedlist_parent; + /* Copy user data, will probably have changed */ + for (i = 0; i < lengthof(current->user_data); i++) { + check->path.node.user_data[i] = current->user_data[i]; + } + // Readd him in the OpenListQueue + aystar->OpenListQueue.push(&aystar->OpenListQueue, check, new_f); + } else { + // A new node, add him to the OpenList + AyStarMain_OpenList_Add(aystar, closedlist_parent, current, new_f, new_g); + } + + return AYSTAR_DONE; +} + +/* + * This function is the core of AyStar. It handles one item and checks + * his neighbour items. If they are valid, they are added to be checked too. + * return values: + * AYSTAR_EMPTY_OPENLIST : indicates all items are tested, and no path + * has been found. + * AYSTAR_LIMIT_REACHED : Indicates that the max_nodes limit has been + * reached. + * AYSTAR_FOUND_END_NODE : indicates we found the end. Path_found now is true, and in path is the path found. + * AYSTAR_STILL_BUSY : indicates we have done this tile, did not found the path yet, and have items left to try. + */ +int AyStarMain_Loop(AyStar *aystar) +{ + int i, r; + + // Get the best node from OpenList + OpenListNode *current = AyStarMain_OpenList_Pop(aystar); + // If empty, drop an error + if (current == NULL) return AYSTAR_EMPTY_OPENLIST; + + // Check for end node and if found, return that code + if (aystar->EndNodeCheck(aystar, current) == AYSTAR_FOUND_END_NODE) { + if (aystar->FoundEndNode != NULL) + aystar->FoundEndNode(aystar, current); + free(current); + return AYSTAR_FOUND_END_NODE; + } + + // Add the node to the ClosedList + AyStarMain_ClosedList_Add(aystar, ¤t->path); + + // Load the neighbours + aystar->GetNeighbours(aystar, current); + + // Go through all neighbours + for (i = 0; i < aystar->num_neighbours; i++) { + // Check and add them to the OpenList if needed + r = aystar->checktile(aystar, &aystar->neighbours[i], current); + } + + // Free the node + free(current); + + if (aystar->max_search_nodes != 0 && Hash_Size(&aystar->ClosedListHash) >= aystar->max_search_nodes) { + /* We've expanded enough nodes */ + return AYSTAR_LIMIT_REACHED; + } else { + // Return that we are still busy + return AYSTAR_STILL_BUSY; + } +} + +/* + * This function frees the memory it allocated + */ +void AyStarMain_Free(AyStar *aystar) +{ + aystar->OpenListQueue.free(&aystar->OpenListQueue, false); + /* 2nd argument above is false, below is true, to free the values only + * once */ + delete_Hash(&aystar->OpenListHash, true); + delete_Hash(&aystar->ClosedListHash, true); +#ifdef AYSTAR_DEBUG + printf("[AyStar] Memory free'd\n"); +#endif +} + +/* + * This function make the memory go back to zero + * This function should be called when you are using the same instance again. + */ +void AyStarMain_Clear(AyStar *aystar) +{ + // Clean the Queue, but not the elements within. That will be done by + // the hash. + aystar->OpenListQueue.clear(&aystar->OpenListQueue, false); + // Clean the hashes + clear_Hash(&aystar->OpenListHash, true); + clear_Hash(&aystar->ClosedListHash, true); + +#ifdef AYSTAR_DEBUG + printf("[AyStar] Cleared AyStar\n"); +#endif +} + +/* + * This is the function you call to run AyStar. + * return values: + * AYSTAR_FOUND_END_NODE : indicates we found an end node. + * AYSTAR_NO_PATH : indicates that there was no path found. + * AYSTAR_STILL_BUSY : indicates we have done some checked, that we did not found the path yet, and that we still have items left to try. + * When the algorithm is done (when the return value is not AYSTAR_STILL_BUSY) + * aystar->clear() is called. Note that when you stop the algorithm halfway, + * you should still call clear() yourself! + */ +int AyStarMain_Main(AyStar *aystar) { + int r, i = 0; + // Loop through the OpenList + // Quit if result is no AYSTAR_STILL_BUSY or is more than loops_per_tick + while ((r = aystar->loop(aystar)) == AYSTAR_STILL_BUSY && (aystar->loops_per_tick == 0 || ++i < aystar->loops_per_tick)) { } +#ifdef AYSTAR_DEBUG + switch (r) { + case AYSTAR_FOUND_END_NODE: printf("[AyStar] Found path!\n"); break; + case AYSTAR_EMPTY_OPENLIST: printf("[AyStar] OpenList run dry, no path found\n"); break; + case AYSTAR_LIMIT_REACHED: printf("[AyStar] Exceeded search_nodes, no path found\n"); break; + default: break; + } +#endif + if (r != AYSTAR_STILL_BUSY) { + /* We're done, clean up */ + _aystar_stats_open_size = aystar->OpenListHash.size; + _aystar_stats_closed_size = aystar->ClosedListHash.size; + aystar->clear(aystar); + } + + switch (r) { + case AYSTAR_FOUND_END_NODE: return AYSTAR_FOUND_END_NODE; + case AYSTAR_EMPTY_OPENLIST: + case AYSTAR_LIMIT_REACHED: return AYSTAR_NO_PATH; + default: return AYSTAR_STILL_BUSY; + } +} + +/* + * Adds a node from where to start an algorithm. Multiple nodes can be added + * if wanted. You should make sure that clear() is called before adding nodes + * if the AyStar has been used before (though the normal main loop calls + * clear() automatically when the algorithm finishes + * g is the cost for starting with this node. + */ +void AyStarMain_AddStartNode(AyStar *aystar, AyStarNode *start_node, uint g) +{ +#ifdef AYSTAR_DEBUG + printf("[AyStar] Starting A* Algorithm from node (%d, %d, %d)\n", + TileX(start_node->tile), TileY(start_node->tile), start_node->direction); +#endif + AyStarMain_OpenList_Add(aystar, NULL, start_node, 0, g); +} + +void init_AyStar(AyStar *aystar, Hash_HashProc hash, uint num_buckets) +{ + // Allocated the Hash for the OpenList and ClosedList + init_Hash(&aystar->OpenListHash, hash, num_buckets); + init_Hash(&aystar->ClosedListHash, hash, num_buckets); + + // Set up our sorting queue + // BinaryHeap allocates a block of 1024 nodes + // When thatone gets full it reserves an otherone, till this number + // That is why it can stay this high + init_BinaryHeap(&aystar->OpenListQueue, 102400); + + aystar->addstart = AyStarMain_AddStartNode; + aystar->main = AyStarMain_Main; + aystar->loop = AyStarMain_Loop; + aystar->free = AyStarMain_Free; + aystar->clear = AyStarMain_Clear; + aystar->checktile = AyStarMain_CheckTile; +} diff --git a/src/bmp.c b/src/bmp.c deleted file mode 100644 --- a/src/bmp.c +++ /dev/null @@ -1,378 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "gfx.h" -#include "bmp.h" -#include "macros.h" - -void BmpInitializeBuffer(BmpBuffer *buffer, FILE *file) { - buffer->pos = -1; - buffer->file = file; - buffer->read = 0; - buffer->real_pos = ftell(file); -} - -static inline void AdvanceBuffer(BmpBuffer *buffer) -{ - buffer->read = (int)fread(buffer->data, 1, BMP_BUFFER_SIZE, buffer->file); - buffer->pos = 0; -} - -static inline bool EndOfBuffer(BmpBuffer *buffer) -{ - if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); - return buffer->pos == buffer->read; -} - -static inline byte ReadByte(BmpBuffer *buffer) -{ - if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); - buffer->real_pos++; - return buffer->data[buffer->pos++]; -} - -static inline uint16 ReadWord(BmpBuffer *buffer) -{ - uint16 var = ReadByte(buffer); - return var | (ReadByte(buffer) << 8); -} - -static inline uint32 ReadDword(BmpBuffer *buffer) -{ - uint32 var = ReadWord(buffer); - return var | (ReadWord(buffer) << 16); -} - -static inline void SkipBytes(BmpBuffer *buffer, int bytes) -{ - int i; - for (i = 0; i < bytes; i++) ReadByte(buffer); -} - -static inline void SetStreamOffset(BmpBuffer *buffer, int offset) -{ - fseek(buffer->file, offset, SEEK_SET); - buffer->pos = -1; - buffer->real_pos = offset; - AdvanceBuffer(buffer); -} - -/** - * Reads a 1 bpp uncompressed bitmap - * The bitmap is converted to a 8 bpp bitmap - */ -static inline bool BmpRead1(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint x, y, i; - byte pad = GB(4 - info->width / 8, 0, 2); - byte *pixel_row; - byte b; - for (y = info->height; y > 0; y--) { - x = 0; - pixel_row = &data->bitmap[(y - 1) * info->width]; - while (x < info->width) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - b = ReadByte(buffer); - for (i = 8; i > 0; i--) { - if (x < info->width) *pixel_row++ = GB(b, i - 1, 1); - x++; - } - } - /* Padding for 32 bit align */ - SkipBytes(buffer, pad); - } - return true; -} - -/** - * Reads a 4 bpp uncompressed bitmap - * The bitmap is converted to a 8 bpp bitmap - */ -static inline bool BmpRead4(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint x, y; - byte pad = GB(4 - info->width / 2, 0, 2); - byte *pixel_row; - byte b; - for (y = info->height; y > 0; y--) { - x = 0; - pixel_row = &data->bitmap[(y - 1) * info->width]; - while (x < info->width) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - b = ReadByte(buffer); - *pixel_row++ = GB(b, 4, 4); - x++; - if (x < info->width) { - *pixel_row++ = GB(b, 0, 4); - x++; - } - } - /* Padding for 32 bit align */ - SkipBytes(buffer, pad); - } - return true; -} - -/** - * Reads a 4-bit RLE compressed bitmap - * The bitmap is converted to a 8 bpp bitmap - */ -static inline bool BmpRead4Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint i; - uint x = 0; - uint y = info->height - 1; - byte n, c, b; - byte *pixel = &data->bitmap[y * info->width]; - while (y != 0 || x < info->width) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - n = ReadByte(buffer); - c = ReadByte(buffer); - if (n == 0) { - switch (c) { - case 0: // end of line - x = 0; - pixel = &data->bitmap[--y * info->width]; - break; - case 1: // end of bitmap - x = info->width; - y = 0; - pixel = NULL; - break; - case 2: // delta - x += ReadByte(buffer); - i = ReadByte(buffer); - if (x >= info->width || (y == 0 && i > 0)) return false; - y -= i; - pixel = &data->bitmap[y * info->width + x]; - break; - default: // uncompressed - i = 0; - while (i++ < c) { - if (EndOfBuffer(buffer) || x >= info->width) return false; - b = ReadByte(buffer); - *pixel++ = GB(b, 4, 4); - x++; - if (x < info->width && i++ < c) { - *pixel++ = GB(b, 0, 4); - x++; - } - } - /* Padding for 16 bit align */ - SkipBytes(buffer, ((c + 1) / 2) % 2); - break; - } - } else { - i = 0; - while (i++ < n) { - if (EndOfBuffer(buffer) || x >= info->width) return false; - *pixel++ = GB(c, 4, 4); - x++; - if (x < info->width && i++ < n) { - *pixel++ = GB(c, 0, 4); - x++; - } - } - } - } - return true; -} - -/** - * Reads a 8 bpp bitmap - */ -static inline bool BmpRead8(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint i; - uint y; - byte pad = GB(4 - info->width, 0, 2); - byte *pixel; - for (y = info->height; y > 0; y--) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - pixel = &data->bitmap[(y - 1) * info->width]; - for (i = 0; i < info->width; i++) *pixel++ = ReadByte(buffer); - /* Padding for 32 bit align */ - SkipBytes(buffer, pad); - } - return true; -} - -/** - * Reads a 8-bit RLE compressed bpp bitmap - */ -static inline bool BmpRead8Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint i; - uint x = 0; - uint y = info->height - 1; - byte n, c; - byte *pixel = &data->bitmap[y * info->width]; - while (y != 0 || x < info->width) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - n = ReadByte(buffer); - c = ReadByte(buffer); - if (n == 0) { - switch (c) { - case 0: // end of line - x = 0; - pixel = &data->bitmap[--y * info->width]; - break; - case 1: // end of bitmap - x = info->width; - y = 0; - pixel = NULL; - break; - case 2: // delta - x += ReadByte(buffer); - i = ReadByte(buffer); - if (x >= info->width || (y == 0 && i > 0)) return false; - y -= i; - pixel = &data->bitmap[y * info->width + x]; - break; - default: // uncompressed - if ((x += c) > info->width) return false; - for (i = 0; i < c; i++) *pixel++ = ReadByte(buffer); - /* Padding for 16 bit align */ - SkipBytes(buffer, c % 2); - break; - } - } else { - for (i = 0; i < n; i++) { - if (x >= info->width) return false; - *pixel++ = c; - x++; - } - } - } - return true; -} - -/** - * Reads a 24 bpp uncompressed bitmap - */ -static inline bool BmpRead24(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint x, y; - byte pad = GB(4 - info->width * 3, 0, 2); - byte *pixel_row; - for (y = info->height; y > 0; y--) { - pixel_row = &data->bitmap[(y - 1) * info->width * 3]; - for (x = 0; x < info->width; x++) { - if (EndOfBuffer(buffer)) return false; // the file is shorter than expected - *(pixel_row + 2) = ReadByte(buffer); // green - *(pixel_row + 1) = ReadByte(buffer); // blue - *pixel_row = ReadByte(buffer); // red - pixel_row += 3; - } - /* Padding for 32 bit align */ - SkipBytes(buffer, pad); - } - return true; -} - -/* - * Reads bitmap headers, and palette (if any) - */ -bool BmpReadHeader(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - uint32 header_size; - assert(info != NULL); - - /* Reading BMP header */ - if (ReadWord(buffer) != 0x4D42) return false; // signature should be 'BM' - SkipBytes(buffer, 8); // skip file size and reserved - info->offset = ReadDword(buffer); - - /* Reading info header */ - header_size = ReadDword(buffer); - if (header_size < 12) return false; // info header should be at least 12 bytes long - - info->os2_bmp = (header_size == 12); // OS/2 1.x or windows 2.x info header is 12 bytes long - - if (info->os2_bmp) { - info->width = ReadWord(buffer); - info->height = ReadWord(buffer); - header_size -= 8; - } else { - info->width = ReadDword(buffer); - info->height = ReadDword(buffer); - header_size -= 12; - } - - if (ReadWord(buffer) != 1) return false; // BMP can have only 1 plane - - info->bpp = ReadWord(buffer); - if (info->bpp != 1 && info->bpp != 4 && info->bpp != 8 && info->bpp != 24) { - /* Only 1 bpp, 4 bpp, 8bpp and 24 bpp bitmaps are supported */ - return false; - } - - /* Reads compression method if available in info header*/ - if ((header_size -= 4) >= 4) { - info->compression = ReadDword(buffer); - header_size -= 4; - } - - /* Only 4-bit and 8-bit rle compression is supported */ - if (info->compression > 2 || (info->compression > 0 && !(info->bpp == 4 || info->bpp == 8))) return false; - - if (info->bpp <= 8) { - uint i; - - /* Reads number of colors if available in info header */ - if (header_size >= 16) { - SkipBytes(buffer, 12); // skip image size and resolution - info->palette_size = ReadDword(buffer); // number of colors in palette - SkipBytes(buffer, header_size - 16); // skip the end of info header - } - if (info->palette_size == 0) info->palette_size = 1 << info->bpp; - - data->palette = calloc(info->palette_size, sizeof(*(data->palette))); - if (data->palette == NULL) return false; - - for (i = 0; i < info->palette_size; i++) { - data->palette[i].b = ReadByte(buffer); - data->palette[i].g = ReadByte(buffer); - data->palette[i].r = ReadByte(buffer); - if (!info->os2_bmp) SkipBytes(buffer, 1); // unused - } - } - - return buffer->real_pos <= info->offset; -} - -/* - * Reads the bitmap - * 1 bpp and 4 bpp bitmaps are converted to 8 bpp bitmaps - */ -bool BmpReadBitmap(BmpBuffer *buffer, BmpInfo *info, BmpData *data) -{ - assert(info != NULL && data != NULL); - - data->bitmap = calloc(info->width * info->height, ((info->bpp == 24) ? 3 : 1) * sizeof(byte)); - if (data->bitmap == NULL) return false; - - /* Load image */ - SetStreamOffset(buffer, info->offset); - switch (info->compression) { - case 0: // no compression - switch (info->bpp) { - case 1: return BmpRead1(buffer, info, data); - case 4: return BmpRead4(buffer, info, data); - case 8: return BmpRead8(buffer, info, data); - case 24: return BmpRead24(buffer, info, data); - default: NOT_REACHED(); return false; - } - case 1: return BmpRead8Rle(buffer, info, data); // 8-bit RLE compression - case 2: return BmpRead4Rle(buffer, info, data); // 4-bit RLE compression - default: NOT_REACHED(); return false; - } -} - -void BmpDestroyData(BmpData *data) -{ - assert(data != NULL); - free(data->palette); - free(data->bitmap); -} diff --git a/src/bmp.cpp b/src/bmp.cpp new file mode 100644 --- /dev/null +++ b/src/bmp.cpp @@ -0,0 +1,378 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "gfx.h" +#include "bmp.h" +#include "macros.h" + +void BmpInitializeBuffer(BmpBuffer *buffer, FILE *file) { + buffer->pos = -1; + buffer->file = file; + buffer->read = 0; + buffer->real_pos = ftell(file); +} + +static inline void AdvanceBuffer(BmpBuffer *buffer) +{ + buffer->read = (int)fread(buffer->data, 1, BMP_BUFFER_SIZE, buffer->file); + buffer->pos = 0; +} + +static inline bool EndOfBuffer(BmpBuffer *buffer) +{ + if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); + return buffer->pos == buffer->read; +} + +static inline byte ReadByte(BmpBuffer *buffer) +{ + if (buffer->pos == buffer->read || buffer->pos < 0) AdvanceBuffer(buffer); + buffer->real_pos++; + return buffer->data[buffer->pos++]; +} + +static inline uint16 ReadWord(BmpBuffer *buffer) +{ + uint16 var = ReadByte(buffer); + return var | (ReadByte(buffer) << 8); +} + +static inline uint32 ReadDword(BmpBuffer *buffer) +{ + uint32 var = ReadWord(buffer); + return var | (ReadWord(buffer) << 16); +} + +static inline void SkipBytes(BmpBuffer *buffer, int bytes) +{ + int i; + for (i = 0; i < bytes; i++) ReadByte(buffer); +} + +static inline void SetStreamOffset(BmpBuffer *buffer, int offset) +{ + fseek(buffer->file, offset, SEEK_SET); + buffer->pos = -1; + buffer->real_pos = offset; + AdvanceBuffer(buffer); +} + +/** + * Reads a 1 bpp uncompressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead1(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y, i; + byte pad = GB(4 - info->width / 8, 0, 2); + byte *pixel_row; + byte b; + for (y = info->height; y > 0; y--) { + x = 0; + pixel_row = &data->bitmap[(y - 1) * info->width]; + while (x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + b = ReadByte(buffer); + for (i = 8; i > 0; i--) { + if (x < info->width) *pixel_row++ = GB(b, i - 1, 1); + x++; + } + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 4 bpp uncompressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead4(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y; + byte pad = GB(4 - info->width / 2, 0, 2); + byte *pixel_row; + byte b; + for (y = info->height; y > 0; y--) { + x = 0; + pixel_row = &data->bitmap[(y - 1) * info->width]; + while (x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + b = ReadByte(buffer); + *pixel_row++ = GB(b, 4, 4); + x++; + if (x < info->width) { + *pixel_row++ = GB(b, 0, 4); + x++; + } + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 4-bit RLE compressed bitmap + * The bitmap is converted to a 8 bpp bitmap + */ +static inline bool BmpRead4Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint x = 0; + uint y = info->height - 1; + byte n, c, b; + byte *pixel = &data->bitmap[y * info->width]; + while (y != 0 || x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + n = ReadByte(buffer); + c = ReadByte(buffer); + if (n == 0) { + switch (c) { + case 0: // end of line + x = 0; + pixel = &data->bitmap[--y * info->width]; + break; + case 1: // end of bitmap + x = info->width; + y = 0; + pixel = NULL; + break; + case 2: // delta + x += ReadByte(buffer); + i = ReadByte(buffer); + if (x >= info->width || (y == 0 && i > 0)) return false; + y -= i; + pixel = &data->bitmap[y * info->width + x]; + break; + default: // uncompressed + i = 0; + while (i++ < c) { + if (EndOfBuffer(buffer) || x >= info->width) return false; + b = ReadByte(buffer); + *pixel++ = GB(b, 4, 4); + x++; + if (x < info->width && i++ < c) { + *pixel++ = GB(b, 0, 4); + x++; + } + } + /* Padding for 16 bit align */ + SkipBytes(buffer, ((c + 1) / 2) % 2); + break; + } + } else { + i = 0; + while (i++ < n) { + if (EndOfBuffer(buffer) || x >= info->width) return false; + *pixel++ = GB(c, 4, 4); + x++; + if (x < info->width && i++ < n) { + *pixel++ = GB(c, 0, 4); + x++; + } + } + } + } + return true; +} + +/** + * Reads a 8 bpp bitmap + */ +static inline bool BmpRead8(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint y; + byte pad = GB(4 - info->width, 0, 2); + byte *pixel; + for (y = info->height; y > 0; y--) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + pixel = &data->bitmap[(y - 1) * info->width]; + for (i = 0; i < info->width; i++) *pixel++ = ReadByte(buffer); + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/** + * Reads a 8-bit RLE compressed bpp bitmap + */ +static inline bool BmpRead8Rle(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint i; + uint x = 0; + uint y = info->height - 1; + byte n, c; + byte *pixel = &data->bitmap[y * info->width]; + while (y != 0 || x < info->width) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + n = ReadByte(buffer); + c = ReadByte(buffer); + if (n == 0) { + switch (c) { + case 0: // end of line + x = 0; + pixel = &data->bitmap[--y * info->width]; + break; + case 1: // end of bitmap + x = info->width; + y = 0; + pixel = NULL; + break; + case 2: // delta + x += ReadByte(buffer); + i = ReadByte(buffer); + if (x >= info->width || (y == 0 && i > 0)) return false; + y -= i; + pixel = &data->bitmap[y * info->width + x]; + break; + default: // uncompressed + if ((x += c) > info->width) return false; + for (i = 0; i < c; i++) *pixel++ = ReadByte(buffer); + /* Padding for 16 bit align */ + SkipBytes(buffer, c % 2); + break; + } + } else { + for (i = 0; i < n; i++) { + if (x >= info->width) return false; + *pixel++ = c; + x++; + } + } + } + return true; +} + +/** + * Reads a 24 bpp uncompressed bitmap + */ +static inline bool BmpRead24(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint x, y; + byte pad = GB(4 - info->width * 3, 0, 2); + byte *pixel_row; + for (y = info->height; y > 0; y--) { + pixel_row = &data->bitmap[(y - 1) * info->width * 3]; + for (x = 0; x < info->width; x++) { + if (EndOfBuffer(buffer)) return false; // the file is shorter than expected + *(pixel_row + 2) = ReadByte(buffer); // green + *(pixel_row + 1) = ReadByte(buffer); // blue + *pixel_row = ReadByte(buffer); // red + pixel_row += 3; + } + /* Padding for 32 bit align */ + SkipBytes(buffer, pad); + } + return true; +} + +/* + * Reads bitmap headers, and palette (if any) + */ +bool BmpReadHeader(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + uint32 header_size; + assert(info != NULL); + + /* Reading BMP header */ + if (ReadWord(buffer) != 0x4D42) return false; // signature should be 'BM' + SkipBytes(buffer, 8); // skip file size and reserved + info->offset = ReadDword(buffer); + + /* Reading info header */ + header_size = ReadDword(buffer); + if (header_size < 12) return false; // info header should be at least 12 bytes long + + info->os2_bmp = (header_size == 12); // OS/2 1.x or windows 2.x info header is 12 bytes long + + if (info->os2_bmp) { + info->width = ReadWord(buffer); + info->height = ReadWord(buffer); + header_size -= 8; + } else { + info->width = ReadDword(buffer); + info->height = ReadDword(buffer); + header_size -= 12; + } + + if (ReadWord(buffer) != 1) return false; // BMP can have only 1 plane + + info->bpp = ReadWord(buffer); + if (info->bpp != 1 && info->bpp != 4 && info->bpp != 8 && info->bpp != 24) { + /* Only 1 bpp, 4 bpp, 8bpp and 24 bpp bitmaps are supported */ + return false; + } + + /* Reads compression method if available in info header*/ + if ((header_size -= 4) >= 4) { + info->compression = ReadDword(buffer); + header_size -= 4; + } + + /* Only 4-bit and 8-bit rle compression is supported */ + if (info->compression > 2 || (info->compression > 0 && !(info->bpp == 4 || info->bpp == 8))) return false; + + if (info->bpp <= 8) { + uint i; + + /* Reads number of colors if available in info header */ + if (header_size >= 16) { + SkipBytes(buffer, 12); // skip image size and resolution + info->palette_size = ReadDword(buffer); // number of colors in palette + SkipBytes(buffer, header_size - 16); // skip the end of info header + } + if (info->palette_size == 0) info->palette_size = 1 << info->bpp; + + data->palette = calloc(info->palette_size, sizeof(*(data->palette))); + if (data->palette == NULL) return false; + + for (i = 0; i < info->palette_size; i++) { + data->palette[i].b = ReadByte(buffer); + data->palette[i].g = ReadByte(buffer); + data->palette[i].r = ReadByte(buffer); + if (!info->os2_bmp) SkipBytes(buffer, 1); // unused + } + } + + return buffer->real_pos <= info->offset; +} + +/* + * Reads the bitmap + * 1 bpp and 4 bpp bitmaps are converted to 8 bpp bitmaps + */ +bool BmpReadBitmap(BmpBuffer *buffer, BmpInfo *info, BmpData *data) +{ + assert(info != NULL && data != NULL); + + data->bitmap = calloc(info->width * info->height, ((info->bpp == 24) ? 3 : 1) * sizeof(byte)); + if (data->bitmap == NULL) return false; + + /* Load image */ + SetStreamOffset(buffer, info->offset); + switch (info->compression) { + case 0: // no compression + switch (info->bpp) { + case 1: return BmpRead1(buffer, info, data); + case 4: return BmpRead4(buffer, info, data); + case 8: return BmpRead8(buffer, info, data); + case 24: return BmpRead24(buffer, info, data); + default: NOT_REACHED(); return false; + } + case 1: return BmpRead8Rle(buffer, info, data); // 8-bit RLE compression + case 2: return BmpRead4Rle(buffer, info, data); // 4-bit RLE compression + default: NOT_REACHED(); return false; + } +} + +void BmpDestroyData(BmpData *data) +{ + assert(data != NULL); + free(data->palette); + free(data->bitmap); +} diff --git a/src/bridge_gui.c b/src/bridge_gui.c deleted file mode 100644 --- a/src/bridge_gui.c +++ /dev/null @@ -1,167 +0,0 @@ -/* $Id$ */ - -/** @file bridge_gui.c Graphical user interface for bridge construction*/ - -#include "stdafx.h" -#include "openttd.h" -#include "table/strings.h" -#include "functions.h" -#include "map.h" -#include "window.h" -#include "gui.h" -#include "viewport.h" -#include "gfx.h" -#include "command.h" -#include "sound.h" -#include "variables.h" -#include "bridge.h" - -static struct BridgeData { - uint count; - TileIndex start_tile; - TileIndex end_tile; - byte type; - byte indexes[MAX_BRIDGES]; - int32 costs[MAX_BRIDGES]; -} _bridgedata; - -void CcBuildBridge(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - if (success) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, tile); -} - -static void BuildBridge(Window *w, int i) -{ - DeleteWindow(w); - DoCommandP(_bridgedata.end_tile, _bridgedata.start_tile, - _bridgedata.indexes[i] | (_bridgedata.type << 8), CcBuildBridge, - CMD_BUILD_BRIDGE | CMD_AUTO | CMD_MSG(STR_5015_CAN_T_BUILD_BRIDGE_HERE)); -} - -static void BuildBridgeWndProc(Window *w, WindowEvent *e) -{ - switch (e->event) { - case WE_PAINT: { - uint i; - - DrawWindowWidgets(w); - - for (i = 0; i < 4 && i + w->vscroll.pos < _bridgedata.count; i++) { - const Bridge *b = &_bridge[_bridgedata.indexes[i + w->vscroll.pos]]; - - SetDParam(2, _bridgedata.costs[i + w->vscroll.pos]); - SetDParam(1, b->speed); - SetDParam(0, b->material); - DrawSprite(b->sprite, 3, 15 + i * 22); - - DrawString(44, 15 + i * 22 , STR_500D, 0); - } - } break; - - case WE_KEYPRESS: { - uint i = e->we.keypress.keycode - '1'; - if (i < 9 && i < _bridgedata.count) { - e->we.keypress.cont = false; - BuildBridge(w, i); - } - - break; - } - - case WE_CLICK: - if (e->we.click.widget == 2) { - uint ind = ((int)e->we.click.pt.y - 14) / 22; - if (ind < 4 && (ind += w->vscroll.pos) < _bridgedata.count) - BuildBridge(w, ind); - } - break; - } -} - -static const Widget _build_bridge_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 7, 11, 199, 0, 13, STR_100D_SELECT_RAIL_BRIDGE, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{ WWT_MATRIX, RESIZE_NONE, 7, 0, 187, 14, 101, 0x401, STR_101F_BRIDGE_SELECTION_CLICK}, -{ WWT_SCROLLBAR, RESIZE_NONE, 7, 188, 199, 14, 101, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, -{ WIDGETS_END}, -}; - -static const WindowDesc _build_bridge_desc = { - WDP_AUTO, WDP_AUTO, 200, 102, - WC_BUILD_BRIDGE, WC_BUILD_TOOLBAR, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, - _build_bridge_widgets, - BuildBridgeWndProc -}; - - -static const Widget _build_road_bridge_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 7, 11, 199, 0, 13, STR_1803_SELECT_ROAD_BRIDGE, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{ WWT_MATRIX, RESIZE_NONE, 7, 0, 187, 14, 101, 0x401, STR_101F_BRIDGE_SELECTION_CLICK}, -{ WWT_SCROLLBAR, RESIZE_NONE, 7, 188, 199, 14, 101, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, -{ WIDGETS_END}, -}; - -static const WindowDesc _build_road_bridge_desc = { - WDP_AUTO, WDP_AUTO, 200, 102, - WC_BUILD_BRIDGE, WC_BUILD_TOOLBAR, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, - _build_road_bridge_widgets, - BuildBridgeWndProc -}; - - -void ShowBuildBridgeWindow(TileIndex start, TileIndex end, byte bridge_type) -{ - uint j = 0; - int32 ret; - StringID errmsg; - - DeleteWindowById(WC_BUILD_BRIDGE, 0); - - _bridgedata.type = bridge_type; - _bridgedata.start_tile = start; - _bridgedata.end_tile = end; - - errmsg = INVALID_STRING_ID; - - // only query bridge building possibility once, result is the same for all bridges! - // returns CMD_ERROR on failure, and price on success - ret = DoCommand(end, start, (bridge_type << 8), DC_AUTO | DC_QUERY_COST, CMD_BUILD_BRIDGE); - - if (CmdFailed(ret)) { - errmsg = _error_message; - } else { - // check which bridges can be built - int bridge_len; // length of the middle parts of the bridge - int tot_bridgedata_len; // total length of bridge - - // get absolute bridge length - bridge_len = GetBridgeLength(start, end); - tot_bridgedata_len = bridge_len + 2; - - tot_bridgedata_len = CalcBridgeLenCostFactor(tot_bridgedata_len); - - for (bridge_type = 0; bridge_type != MAX_BRIDGES; bridge_type++) { // loop for all bridgetypes - if (CheckBridge_Stuff(bridge_type, bridge_len)) { - const Bridge *b = &_bridge[bridge_type]; - // bridge is accepted, add to list - // add to terraforming & bulldozing costs the cost of the bridge itself (not computed with DC_QUERY_COST) - _bridgedata.costs[j] = ret + (((int64)tot_bridgedata_len * _price.build_bridge * b->price) >> 8); - _bridgedata.indexes[j] = bridge_type; - j++; - } - } - } - - _bridgedata.count = j; - - if (j != 0) { - Window *w = AllocateWindowDesc((_bridgedata.type & 0x80) ? &_build_road_bridge_desc : &_build_bridge_desc); - w->vscroll.cap = 4; - w->vscroll.count = (byte)j; - } else { - ShowErrorMessage(errmsg, STR_5015_CAN_T_BUILD_BRIDGE_HERE, TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE); - } -} diff --git a/src/bridge_gui.cpp b/src/bridge_gui.cpp new file mode 100644 --- /dev/null +++ b/src/bridge_gui.cpp @@ -0,0 +1,167 @@ +/* $Id$ */ + +/** @file bridge_gui.c Graphical user interface for bridge construction*/ + +#include "stdafx.h" +#include "openttd.h" +#include "table/strings.h" +#include "functions.h" +#include "map.h" +#include "window.h" +#include "gui.h" +#include "viewport.h" +#include "gfx.h" +#include "command.h" +#include "sound.h" +#include "variables.h" +#include "bridge.h" + +static struct BridgeData { + uint count; + TileIndex start_tile; + TileIndex end_tile; + byte type; + byte indexes[MAX_BRIDGES]; + int32 costs[MAX_BRIDGES]; +} _bridgedata; + +void CcBuildBridge(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + if (success) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, tile); +} + +static void BuildBridge(Window *w, int i) +{ + DeleteWindow(w); + DoCommandP(_bridgedata.end_tile, _bridgedata.start_tile, + _bridgedata.indexes[i] | (_bridgedata.type << 8), CcBuildBridge, + CMD_BUILD_BRIDGE | CMD_AUTO | CMD_MSG(STR_5015_CAN_T_BUILD_BRIDGE_HERE)); +} + +static void BuildBridgeWndProc(Window *w, WindowEvent *e) +{ + switch (e->event) { + case WE_PAINT: { + uint i; + + DrawWindowWidgets(w); + + for (i = 0; i < 4 && i + w->vscroll.pos < _bridgedata.count; i++) { + const Bridge *b = &_bridge[_bridgedata.indexes[i + w->vscroll.pos]]; + + SetDParam(2, _bridgedata.costs[i + w->vscroll.pos]); + SetDParam(1, b->speed); + SetDParam(0, b->material); + DrawSprite(b->sprite, 3, 15 + i * 22); + + DrawString(44, 15 + i * 22 , STR_500D, 0); + } + } break; + + case WE_KEYPRESS: { + uint i = e->we.keypress.keycode - '1'; + if (i < 9 && i < _bridgedata.count) { + e->we.keypress.cont = false; + BuildBridge(w, i); + } + + break; + } + + case WE_CLICK: + if (e->we.click.widget == 2) { + uint ind = ((int)e->we.click.pt.y - 14) / 22; + if (ind < 4 && (ind += w->vscroll.pos) < _bridgedata.count) + BuildBridge(w, ind); + } + break; + } +} + +static const Widget _build_bridge_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 199, 0, 13, STR_100D_SELECT_RAIL_BRIDGE, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_MATRIX, RESIZE_NONE, 7, 0, 187, 14, 101, 0x401, STR_101F_BRIDGE_SELECTION_CLICK}, +{ WWT_SCROLLBAR, RESIZE_NONE, 7, 188, 199, 14, 101, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +{ WIDGETS_END}, +}; + +static const WindowDesc _build_bridge_desc = { + WDP_AUTO, WDP_AUTO, 200, 102, + WC_BUILD_BRIDGE, WC_BUILD_TOOLBAR, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _build_bridge_widgets, + BuildBridgeWndProc +}; + + +static const Widget _build_road_bridge_widgets[] = { +{ WWT_CLOSEBOX, RESIZE_NONE, 7, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 7, 11, 199, 0, 13, STR_1803_SELECT_ROAD_BRIDGE, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_MATRIX, RESIZE_NONE, 7, 0, 187, 14, 101, 0x401, STR_101F_BRIDGE_SELECTION_CLICK}, +{ WWT_SCROLLBAR, RESIZE_NONE, 7, 188, 199, 14, 101, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST}, +{ WIDGETS_END}, +}; + +static const WindowDesc _build_road_bridge_desc = { + WDP_AUTO, WDP_AUTO, 200, 102, + WC_BUILD_BRIDGE, WC_BUILD_TOOLBAR, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET, + _build_road_bridge_widgets, + BuildBridgeWndProc +}; + + +void ShowBuildBridgeWindow(TileIndex start, TileIndex end, byte bridge_type) +{ + uint j = 0; + int32 ret; + StringID errmsg; + + DeleteWindowById(WC_BUILD_BRIDGE, 0); + + _bridgedata.type = bridge_type; + _bridgedata.start_tile = start; + _bridgedata.end_tile = end; + + errmsg = INVALID_STRING_ID; + + // only query bridge building possibility once, result is the same for all bridges! + // returns CMD_ERROR on failure, and price on success + ret = DoCommand(end, start, (bridge_type << 8), DC_AUTO | DC_QUERY_COST, CMD_BUILD_BRIDGE); + + if (CmdFailed(ret)) { + errmsg = _error_message; + } else { + // check which bridges can be built + int bridge_len; // length of the middle parts of the bridge + int tot_bridgedata_len; // total length of bridge + + // get absolute bridge length + bridge_len = GetBridgeLength(start, end); + tot_bridgedata_len = bridge_len + 2; + + tot_bridgedata_len = CalcBridgeLenCostFactor(tot_bridgedata_len); + + for (bridge_type = 0; bridge_type != MAX_BRIDGES; bridge_type++) { // loop for all bridgetypes + if (CheckBridge_Stuff(bridge_type, bridge_len)) { + const Bridge *b = &_bridge[bridge_type]; + // bridge is accepted, add to list + // add to terraforming & bulldozing costs the cost of the bridge itself (not computed with DC_QUERY_COST) + _bridgedata.costs[j] = ret + (((int64)tot_bridgedata_len * _price.build_bridge * b->price) >> 8); + _bridgedata.indexes[j] = bridge_type; + j++; + } + } + } + + _bridgedata.count = j; + + if (j != 0) { + Window *w = AllocateWindowDesc((_bridgedata.type & 0x80) ? &_build_road_bridge_desc : &_build_bridge_desc); + w->vscroll.cap = 4; + w->vscroll.count = (byte)j; + } else { + ShowErrorMessage(errmsg, STR_5015_CAN_T_BUILD_BRIDGE_HERE, TileX(end) * TILE_SIZE, TileY(end) * TILE_SIZE); + } +} diff --git a/src/bridge_map.c b/src/bridge_map.c deleted file mode 100644 --- a/src/bridge_map.c +++ /dev/null @@ -1,51 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "bridge_map.h" -#include "variables.h" - - -TileIndex GetBridgeEnd(TileIndex tile, DiagDirection dir) -{ - TileIndexDiff delta = TileOffsByDiagDir(dir); - - dir = ReverseDiagDir(dir); - do { - tile += delta; - } while (!IsBridgeTile(tile) || GetBridgeRampDirection(tile) != dir); - - return tile; -} - - -TileIndex GetNorthernBridgeEnd(TileIndex t) -{ - return GetBridgeEnd(t, ReverseDiagDir(AxisToDiagDir(GetBridgeAxis(t)))); -} - - -TileIndex GetSouthernBridgeEnd(TileIndex t) -{ - return GetBridgeEnd(t, AxisToDiagDir(GetBridgeAxis(t))); -} - - -TileIndex GetOtherBridgeEnd(TileIndex tile) -{ - assert(IsBridgeTile(tile)); - return GetBridgeEnd(tile, GetBridgeRampDirection(tile)); -} - -uint GetBridgeHeight(TileIndex t) -{ - uint h; - uint tileh = GetTileSlope(t, &h); - uint f = GetBridgeFoundation(tileh, DiagDirToAxis(GetBridgeRampDirection(t))); - - // one height level extra if the ramp is on a flat foundation - return - h + TILE_HEIGHT + - (IS_INT_INSIDE(f, 1, 15) ? TILE_HEIGHT : 0) + - (IsSteepSlope(tileh) ? TILE_HEIGHT : 0); -} diff --git a/src/bridge_map.cpp b/src/bridge_map.cpp new file mode 100644 --- /dev/null +++ b/src/bridge_map.cpp @@ -0,0 +1,51 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "bridge_map.h" +#include "variables.h" + + +TileIndex GetBridgeEnd(TileIndex tile, DiagDirection dir) +{ + TileIndexDiff delta = TileOffsByDiagDir(dir); + + dir = ReverseDiagDir(dir); + do { + tile += delta; + } while (!IsBridgeTile(tile) || GetBridgeRampDirection(tile) != dir); + + return tile; +} + + +TileIndex GetNorthernBridgeEnd(TileIndex t) +{ + return GetBridgeEnd(t, ReverseDiagDir(AxisToDiagDir(GetBridgeAxis(t)))); +} + + +TileIndex GetSouthernBridgeEnd(TileIndex t) +{ + return GetBridgeEnd(t, AxisToDiagDir(GetBridgeAxis(t))); +} + + +TileIndex GetOtherBridgeEnd(TileIndex tile) +{ + assert(IsBridgeTile(tile)); + return GetBridgeEnd(tile, GetBridgeRampDirection(tile)); +} + +uint GetBridgeHeight(TileIndex t) +{ + uint h; + uint tileh = GetTileSlope(t, &h); + uint f = GetBridgeFoundation(tileh, DiagDirToAxis(GetBridgeRampDirection(t))); + + // one height level extra if the ramp is on a flat foundation + return + h + TILE_HEIGHT + + (IS_INT_INSIDE(f, 1, 15) ? TILE_HEIGHT : 0) + + (IsSteepSlope(tileh) ? TILE_HEIGHT : 0); +} diff --git a/src/build_vehicle_gui.c b/src/build_vehicle_gui.c deleted file mode 100644 --- a/src/build_vehicle_gui.c +++ /dev/null @@ -1,492 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "aircraft.h" -#include "debug.h" -#include "functions.h" -#include "table/sprites.h" -#include "table/strings.h" -#include "window.h" -#include "gui.h" -#include "vehicle.h" -#include "gfx.h" -#include "station.h" -#include "command.h" -#include "engine.h" -#include "player.h" -#include "depot.h" -#include "airport.h" -#include "vehicle_gui.h" -#include "newgrf_engine.h" -#include "date.h" -#include "strings.h" - - -enum BuildVehicleWidgets { - BUILD_VEHICLE_WIDGET_CLOSEBOX = 0, - BUILD_VEHICLE_WIDGET_CAPTION, - BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING, - BUILD_VEHICLE_WIDGET_SORT_TEXT, - BUILD_VEHICLE_WIDGET_SORT_DROPDOWN, - BUILD_VEHICLE_WIDGET_LIST, - BUILD_VEHICLE_WIDGET_SCROLLBAR, - BUILD_VEHICLE_WIDGET_PANEL, - BUILD_VEHICLE_WIDGET_BUILD, - BUILD_VEHICLE_WIDGET_RENAME, - BUILD_VEHICLE_WIDGET_RESIZE, -}; - -static const Widget _build_vehicle_widgets[] = { - { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, - { WWT_CAPTION, RESIZE_NONE, 14, 11, 239, 0, 13, STR_A005_NEW_AIRCRAFT, STR_018C_WINDOW_TITLE_DRAG_THIS }, - { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 80, 14, 25, STR_SORT_BY, STR_SORT_ORDER_TIP}, - { WWT_PANEL, RESIZE_NONE, 14, 81, 227, 14, 25, 0x0, STR_SORT_CRITERIA_TIP}, - { WWT_TEXTBTN, RESIZE_NONE, 14, 228, 239, 14, 25, STR_0225, STR_SORT_CRITERIA_TIP}, - { WWT_MATRIX, RESIZE_BOTTOM, 14, 0, 227, 26, 121, 0x401, STR_A025_AIRCRAFT_SELECTION_LIST }, - { WWT_SCROLLBAR, RESIZE_BOTTOM, 14, 228, 239, 26, 121, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST }, - { WWT_PANEL, RESIZE_TB, 14, 0, 239, 122, 213, 0x0, STR_NULL }, - - { WWT_PUSHTXTBTN, RESIZE_TB, 14, 0, 114, 214, 225, STR_A006_BUILD_AIRCRAFT, STR_A026_BUILD_THE_HIGHLIGHTED_AIRCRAFT }, - { WWT_PUSHTXTBTN, RESIZE_TB, 14, 115, 227, 214, 225, STR_A037_RENAME, STR_A038_RENAME_AIRCRAFT_TYPE }, - { WWT_RESIZEBOX, RESIZE_TB, 14, 228, 239, 214, 225, 0x0, STR_RESIZE_BUTTON }, - { WIDGETS_END}, -}; - -static bool _internal_sort_order; // descending/ascending -static byte _last_sort_criteria = 0; -static bool _last_sort_order = false; - -static int CDECL EngineNumberSorter(const void *a, const void *b) -{ - const EngineID va = *(const EngineID*)a; - const EngineID vb = *(const EngineID*)b; - int r = va - vb; - - return _internal_sort_order ? -r : r; -} - -static int CDECL EngineIntroDateSorter(const void *a, const void *b) -{ - const int va = GetEngine(*(const EngineID*)a)->intro_date; - const int vb = GetEngine(*(const EngineID*)b)->intro_date; - const int r = va - vb; - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -static int CDECL EngineNameSorter(const void *a, const void *b) -{ - static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE }; - static char last_name[2][64] = { "\0", "\0" }; - - const EngineID va = *(const EngineID*)a; - const EngineID vb = *(const EngineID*)b; - int r; - - if (va != last_engine[0]) { - last_engine[0] = va; - GetString(last_name[0], GetCustomEngineName(va), lastof(last_name[0])); - } - - if (vb != last_engine[1]) { - last_engine[1] = vb; - GetString(last_name[1], GetCustomEngineName(vb), lastof(last_name[1])); - } - - r = strcmp(last_name[0], last_name[1]); // sort by name - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -static int CDECL EngineReliabilitySorter(const void *a, const void *b) -{ - const int va = GetEngine(*(const EngineID*)a)->reliability; - const int vb = GetEngine(*(const EngineID*)b)->reliability; - const int r = va - vb; - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -/* Aircraft sorting functions */ - -static int CDECL AircraftEngineCostSorter(const void *a, const void *b) -{ - const int va = AircraftVehInfo(*(const EngineID*)a)->base_cost; - const int vb = AircraftVehInfo(*(const EngineID*)b)->base_cost; - int r = va - vb; - - return _internal_sort_order ? -r : r; -} - -static int CDECL AircraftEngineSpeedSorter(const void *a, const void *b) -{ - const int va = AircraftVehInfo(*(const EngineID*)a)->max_speed; - const int vb = AircraftVehInfo(*(const EngineID*)b)->max_speed; - const int r = va - vb; - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -static int CDECL AircraftEngineRunningCostSorter(const void *a, const void *b) -{ - const int va = AircraftVehInfo(*(const EngineID*)a)->running_cost; - const int vb = AircraftVehInfo(*(const EngineID*)b)->running_cost; - const int r = va - vb; - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -static int CDECL AircraftEngineCargoSorter(const void *a, const void *b) -{ - const int va = AircraftVehInfo(*(const EngineID*)a)->passenger_capacity; - const int vb = AircraftVehInfo(*(const EngineID*)b)->passenger_capacity; - const int r = va - vb; - - if (r == 0) { - /* Use EngineID to sort instead since we want consistent sorting */ - return EngineNumberSorter(a, b); - } - return _internal_sort_order ? -r : r; -} - -static EngList_SortTypeFunction * const _aircraft_sorter[] = { - &EngineNumberSorter, - &AircraftEngineCostSorter, - &AircraftEngineSpeedSorter, - &EngineIntroDateSorter, - &EngineNameSorter, - &AircraftEngineRunningCostSorter, - &EngineReliabilitySorter, - &AircraftEngineCargoSorter, -}; - -static const StringID _aircraft_sort_listing[] = { - STR_ENGINE_SORT_ENGINE_ID, - STR_ENGINE_SORT_COST, - STR_SORT_BY_MAX_SPEED, - STR_ENGINE_SORT_INTRO_DATE, - STR_SORT_BY_DROPDOWN_NAME, - STR_ENGINE_SORT_RUNNING_COST, - STR_SORT_BY_RELIABILITY, - STR_ENGINE_SORT_CARGO_CAPACITY, - INVALID_STRING_ID -}; - - -/** -* Draw the purchase info details of an aircraft at a given location. - * @param x,y location where to draw the info - * @param engine_number the engine of which to draw the info of - */ -void DrawAircraftPurchaseInfo(int x, int y, uint w, EngineID engine_number) -{ - const AircraftVehicleInfo *avi = AircraftVehInfo(engine_number); - const Engine *e = GetEngine(engine_number); - CargoID cargo; - YearMonthDay ymd; - ConvertDateToYMD(e->intro_date, &ymd); - - /* Purchase cost - Max speed */ - SetDParam(0, avi->base_cost * (_price.aircraft_base>>3)>>5); - SetDParam(1, avi->max_speed * 128 / 10); - DrawString(x, y, STR_PURCHASE_INFO_COST_SPEED, 0); - y += 10; - - /* Cargo capacity */ - cargo = FindFirstRefittableCargo(engine_number); - if (cargo == CT_INVALID || cargo == CT_PASSENGERS) { - SetDParam(0, avi->passenger_capacity); - SetDParam(1, avi->mail_capacity); - DrawString(x, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY, 0); - } else { - /* Note, if the default capacity is selected by the refit capacity - * callback, then the capacity shown is likely to be incorrect. */ - SetDParam(0, cargo); - SetDParam(1, AircraftDefaultCargoCapacity(cargo, engine_number)); - SetDParam(2, STR_9842_REFITTABLE); - DrawString(x, y, STR_PURCHASE_INFO_CAPACITY, 0); - } - y += 10; - - /* Running cost */ - SetDParam(0, avi->running_cost * _price.aircraft_running >> 8); - DrawString(x, y, STR_PURCHASE_INFO_RUNNINGCOST, 0); - y += 10; - - /* Design date - Life length */ - SetDParam(0, ymd.year); - SetDParam(1, e->lifelength); - DrawString(x, y, STR_PURCHASE_INFO_DESIGNED_LIFE, 0); - y += 10; - - /* Reliability */ - SetDParam(0, e->reliability * 100 >> 16); - DrawString(x, y, STR_PURCHASE_INFO_RELIABILITY, 0); - y += 10; - - /* Additional text from NewGRF */ - y += ShowAdditionalText(x, y, w, engine_number); - y += ShowRefitOptionsList(x, y, w, engine_number); -} - -void DrawAircraftImage(const Vehicle *v, int x, int y, VehicleID selection) -{ - PalSpriteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v); - DrawSprite(GetAircraftImage(v, DIR_W) | pal, x + 25, y + 10); - if (v->subtype == 0) { - SpriteID rotor_sprite = GetCustomRotorSprite(v, true); - if (rotor_sprite == 0) rotor_sprite = SPR_ROTOR_STOPPED; - DrawSprite(rotor_sprite, x + 25, y + 5); - } - if (v->index == selection) { - DrawFrameRect(x - 1, y - 1, x + 58, y + 21, 0xF, FR_BORDERONLY); - } -} - -void CcBuildAircraft(bool success, TileIndex tile, uint32 p1, uint32 p2) -{ - if (success) { - const Vehicle *v = GetVehicle(_new_vehicle_id); - - if (v->tile == _backup_orders_tile) { - _backup_orders_tile = 0; - RestoreVehicleOrders(v, _backup_orders_data); - } - ShowAircraftViewWindow(v); - } -} - -static void GenerateBuildAircraftList(Window *w) -{ - EngineID eid, sel_id; - buildvehicle_d *bv = &WP(w, buildvehicle_d); - - EngList_RemoveAll(&bv->eng_list); - - /* Make list of all available planes. - * Also check to see if the previously selected plane is still available, - * and if not, reset selection to INVALID_ENGINE. This could be the case - * when planes become obsolete and are removed */ - sel_id = INVALID_ENGINE; - for (eid = AIRCRAFT_ENGINES_INDEX; eid < AIRCRAFT_ENGINES_INDEX + NUM_AIRCRAFT_ENGINES; eid++) { - if (IsEngineBuildable(eid, VEH_Aircraft, _local_player)) { - const AircraftVehicleInfo *avi = AircraftVehInfo(eid); - switch (bv->filter.acc_planes) { - case HELICOPTERS_ONLY: - if (avi->subtype != 0) continue; // if not helicopter - break; - - case AIRCRAFT_ONLY: - if (avi->subtype == 0) continue; // if helicopter - break; - - case ALL: break; - } - EngList_Add(&bv->eng_list, eid); - - if (eid == bv->sel_engine) sel_id = eid; - } - } - - bv->sel_engine = sel_id; -} - -static void GenerateBuildList(Window *w) -{ - buildvehicle_d *bv = &WP(w, buildvehicle_d); - - switch (bv->vehicle_type) { - case VEH_Aircraft: - GenerateBuildAircraftList(w); - _internal_sort_order = bv->descending_sort_order; - EngList_Sort(&bv->eng_list, _aircraft_sorter[bv->sort_criteria]); - break; - - default: NOT_REACHED(); - } -} - -static void DrawBuildAircraftWindow(Window *w) -{ - const buildvehicle_d *bv = &WP(w, buildvehicle_d); - - SetWindowWidgetDisabledState(w, BUILD_VEHICLE_WIDGET_BUILD, w->window_number == 0); - - SetVScrollCount(w, EngList_Count(&bv->eng_list)); - DrawWindowWidgets(w); - - { - int x = 2; - int y = 27; - EngineID selected_id = bv->sel_engine; - EngineID eid = w->vscroll.pos; - uint16 max = min(w->vscroll.pos + w->vscroll.cap, EngList_Count(&bv->eng_list)); - - for (; eid < max; eid++) { - const EngineID engine = bv->eng_list[eid]; - - DrawString(x + 62, y + 7, GetCustomEngineName(engine), engine == selected_id ? 0xC : 0x10); - DrawAircraftEngine(x + 29, y + 10, engine, GetEnginePalette(engine, _local_player)); - y += 24; - } - - if (selected_id != INVALID_ENGINE) { - const Widget *wi = &w->widget[BUILD_VEHICLE_WIDGET_PANEL]; - DrawAircraftPurchaseInfo(x, wi->top + 1, wi->right - wi->left - 2, selected_id); - } - } - DrawString(85, 15, _aircraft_sort_listing[bv->sort_criteria], 0x10); - DoDrawString(bv->descending_sort_order ? DOWNARROW : UPARROW, 69, 15, 0x10); -} - -static void BuildAircraftClickEvent(Window *w, WindowEvent *e) -{ - buildvehicle_d *bv = &WP(w, buildvehicle_d); - - switch (e->we.click.widget) { - case BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING: - bv->descending_sort_order ^= true; - _last_sort_order = bv->descending_sort_order; - GenerateBuildList(w); - SetWindowDirty(w); - break; - - case BUILD_VEHICLE_WIDGET_LIST: { - uint i = (e->we.click.pt.y - 26) / 24 + w->vscroll.pos; - uint num_items = EngList_Count(&bv->eng_list); - bv->sel_engine = (i < num_items) ? bv->eng_list[i] : INVALID_ENGINE; - SetWindowDirty(w); - break; - } - - case BUILD_VEHICLE_WIDGET_SORT_TEXT: case BUILD_VEHICLE_WIDGET_SORT_DROPDOWN:/* Select sorting criteria dropdown menu */ - ShowDropDownMenu(w, _aircraft_sort_listing, bv->sort_criteria, BUILD_VEHICLE_WIDGET_SORT_DROPDOWN, 0, 0); - return; - - case BUILD_VEHICLE_WIDGET_BUILD: { - EngineID sel_eng = bv->sel_engine; - if (sel_eng != INVALID_ENGINE) { - DoCommandP(w->window_number, sel_eng, 0, CcBuildAircraft, CMD_BUILD_AIRCRAFT | CMD_MSG(STR_A008_CAN_T_BUILD_AIRCRAFT)); - } - break; - } - - case BUILD_VEHICLE_WIDGET_RENAME: { - EngineID sel_eng = bv->sel_engine; - if (sel_eng != INVALID_ENGINE) { - bv->rename_engine = sel_eng; - ShowQueryString(GetCustomEngineName(sel_eng), STR_A039_RENAME_AIRCRAFT_TYPE, 31, 160, w, CS_ALPHANUMERAL); - } - break; - } - } -} - -static void NewAircraftWndProc(Window *w, WindowEvent *e) -{ - buildvehicle_d *bv = &WP(w, buildvehicle_d); - - switch (e->event) { - case WE_INVALIDATE_DATA: - GenerateBuildList(w); - break; - - case WE_DESTROY: - EngList_Destroy(&bv->eng_list); - break; - - case WE_PAINT: - DrawBuildAircraftWindow(w); - break; - - case WE_CLICK: - BuildAircraftClickEvent(w, e); - break; - - case WE_ON_EDIT_TEXT: { - if (e->we.edittext.str[0] != '\0') { - _cmd_text = e->we.edittext.str; - DoCommandP(0, bv->rename_engine, 0, NULL, CMD_RENAME_ENGINE | CMD_MSG(STR_A03A_CAN_T_RENAME_AIRCRAFT_TYPE)); - } - break; - } - - case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ - if (bv->sort_criteria != e->we.dropdown.index) { - bv->sort_criteria = _last_sort_criteria = e->we.dropdown.index; - GenerateBuildList(w); - } - SetWindowDirty(w); - break; - - case WE_RESIZE: - w->vscroll.cap += e->we.sizing.diff.y / 24; - w->widget[BUILD_VEHICLE_WIDGET_LIST].data = (w->vscroll.cap << 8) + 1; - break; - } -} - -static const WindowDesc _build_vehicle_desc = { - WDP_AUTO, WDP_AUTO, 240, 226, - WC_BUILD_VEHICLE,0, - WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE, - _build_vehicle_widgets, - NewAircraftWndProc -}; - -void ShowBuildVehicleWindow(TileIndex tile, byte type) -{ - buildvehicle_d *bv; - Window *w; - - DeleteWindowById(WC_BUILD_VEHICLE, tile); - w = AllocateWindowDescFront(&_build_vehicle_desc, tile); - if (w == NULL) return; - - w->caption_color = (tile != 0) ? GetTileOwner(tile) : _local_player; - w->resize.step_height = GetVehicleListHeight(type); - w->vscroll.cap = 4; - w->widget[BUILD_VEHICLE_WIDGET_LIST].data = (w->vscroll.cap << 8) + 1; - - bv = &WP(w, buildvehicle_d); - EngList_Create(&bv->eng_list); - bv->sel_engine = INVALID_ENGINE; - bv->sort_criteria = _last_sort_criteria; - bv->descending_sort_order = _last_sort_order; - - bv->vehicle_type = type; - - switch (type) { - case VEH_Aircraft: { - byte acc_planes = (tile == 0) ? ALL : GetAirport(GetStationByTile(tile)->airport_type)->acc_planes; - bv->filter.acc_planes = acc_planes; - break; - } - default: NOT_REACHED(); - } - - GenerateBuildList(w); - /* Select the first plane in the list as default when opening the window */ - if (EngList_Count(&bv->eng_list) > 0) bv->sel_engine = bv->eng_list[0]; -} diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp new file mode 100644 --- /dev/null +++ b/src/build_vehicle_gui.cpp @@ -0,0 +1,492 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "aircraft.h" +#include "debug.h" +#include "functions.h" +#include "table/sprites.h" +#include "table/strings.h" +#include "window.h" +#include "gui.h" +#include "vehicle.h" +#include "gfx.h" +#include "station.h" +#include "command.h" +#include "engine.h" +#include "player.h" +#include "depot.h" +#include "airport.h" +#include "vehicle_gui.h" +#include "newgrf_engine.h" +#include "date.h" +#include "strings.h" + + +enum BuildVehicleWidgets { + BUILD_VEHICLE_WIDGET_CLOSEBOX = 0, + BUILD_VEHICLE_WIDGET_CAPTION, + BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING, + BUILD_VEHICLE_WIDGET_SORT_TEXT, + BUILD_VEHICLE_WIDGET_SORT_DROPDOWN, + BUILD_VEHICLE_WIDGET_LIST, + BUILD_VEHICLE_WIDGET_SCROLLBAR, + BUILD_VEHICLE_WIDGET_PANEL, + BUILD_VEHICLE_WIDGET_BUILD, + BUILD_VEHICLE_WIDGET_RENAME, + BUILD_VEHICLE_WIDGET_RESIZE, +}; + +static const Widget _build_vehicle_widgets[] = { + { WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW }, + { WWT_CAPTION, RESIZE_NONE, 14, 11, 239, 0, 13, STR_A005_NEW_AIRCRAFT, STR_018C_WINDOW_TITLE_DRAG_THIS }, + { WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 80, 14, 25, STR_SORT_BY, STR_SORT_ORDER_TIP}, + { WWT_PANEL, RESIZE_NONE, 14, 81, 227, 14, 25, 0x0, STR_SORT_CRITERIA_TIP}, + { WWT_TEXTBTN, RESIZE_NONE, 14, 228, 239, 14, 25, STR_0225, STR_SORT_CRITERIA_TIP}, + { WWT_MATRIX, RESIZE_BOTTOM, 14, 0, 227, 26, 121, 0x401, STR_A025_AIRCRAFT_SELECTION_LIST }, + { WWT_SCROLLBAR, RESIZE_BOTTOM, 14, 228, 239, 26, 121, 0x0, STR_0190_SCROLL_BAR_SCROLLS_LIST }, + { WWT_PANEL, RESIZE_TB, 14, 0, 239, 122, 213, 0x0, STR_NULL }, + + { WWT_PUSHTXTBTN, RESIZE_TB, 14, 0, 114, 214, 225, STR_A006_BUILD_AIRCRAFT, STR_A026_BUILD_THE_HIGHLIGHTED_AIRCRAFT }, + { WWT_PUSHTXTBTN, RESIZE_TB, 14, 115, 227, 214, 225, STR_A037_RENAME, STR_A038_RENAME_AIRCRAFT_TYPE }, + { WWT_RESIZEBOX, RESIZE_TB, 14, 228, 239, 214, 225, 0x0, STR_RESIZE_BUTTON }, + { WIDGETS_END}, +}; + +static bool _internal_sort_order; // descending/ascending +static byte _last_sort_criteria = 0; +static bool _last_sort_order = false; + +static int CDECL EngineNumberSorter(const void *a, const void *b) +{ + const EngineID va = *(const EngineID*)a; + const EngineID vb = *(const EngineID*)b; + int r = va - vb; + + return _internal_sort_order ? -r : r; +} + +static int CDECL EngineIntroDateSorter(const void *a, const void *b) +{ + const int va = GetEngine(*(const EngineID*)a)->intro_date; + const int vb = GetEngine(*(const EngineID*)b)->intro_date; + const int r = va - vb; + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +static int CDECL EngineNameSorter(const void *a, const void *b) +{ + static EngineID last_engine[2] = { INVALID_ENGINE, INVALID_ENGINE }; + static char last_name[2][64] = { "\0", "\0" }; + + const EngineID va = *(const EngineID*)a; + const EngineID vb = *(const EngineID*)b; + int r; + + if (va != last_engine[0]) { + last_engine[0] = va; + GetString(last_name[0], GetCustomEngineName(va), lastof(last_name[0])); + } + + if (vb != last_engine[1]) { + last_engine[1] = vb; + GetString(last_name[1], GetCustomEngineName(vb), lastof(last_name[1])); + } + + r = strcmp(last_name[0], last_name[1]); // sort by name + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +static int CDECL EngineReliabilitySorter(const void *a, const void *b) +{ + const int va = GetEngine(*(const EngineID*)a)->reliability; + const int vb = GetEngine(*(const EngineID*)b)->reliability; + const int r = va - vb; + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +/* Aircraft sorting functions */ + +static int CDECL AircraftEngineCostSorter(const void *a, const void *b) +{ + const int va = AircraftVehInfo(*(const EngineID*)a)->base_cost; + const int vb = AircraftVehInfo(*(const EngineID*)b)->base_cost; + int r = va - vb; + + return _internal_sort_order ? -r : r; +} + +static int CDECL AircraftEngineSpeedSorter(const void *a, const void *b) +{ + const int va = AircraftVehInfo(*(const EngineID*)a)->max_speed; + const int vb = AircraftVehInfo(*(const EngineID*)b)->max_speed; + const int r = va - vb; + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +static int CDECL AircraftEngineRunningCostSorter(const void *a, const void *b) +{ + const int va = AircraftVehInfo(*(const EngineID*)a)->running_cost; + const int vb = AircraftVehInfo(*(const EngineID*)b)->running_cost; + const int r = va - vb; + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +static int CDECL AircraftEngineCargoSorter(const void *a, const void *b) +{ + const int va = AircraftVehInfo(*(const EngineID*)a)->passenger_capacity; + const int vb = AircraftVehInfo(*(const EngineID*)b)->passenger_capacity; + const int r = va - vb; + + if (r == 0) { + /* Use EngineID to sort instead since we want consistent sorting */ + return EngineNumberSorter(a, b); + } + return _internal_sort_order ? -r : r; +} + +static EngList_SortTypeFunction * const _aircraft_sorter[] = { + &EngineNumberSorter, + &AircraftEngineCostSorter, + &AircraftEngineSpeedSorter, + &EngineIntroDateSorter, + &EngineNameSorter, + &AircraftEngineRunningCostSorter, + &EngineReliabilitySorter, + &AircraftEngineCargoSorter, +}; + +static const StringID _aircraft_sort_listing[] = { + STR_ENGINE_SORT_ENGINE_ID, + STR_ENGINE_SORT_COST, + STR_SORT_BY_MAX_SPEED, + STR_ENGINE_SORT_INTRO_DATE, + STR_SORT_BY_DROPDOWN_NAME, + STR_ENGINE_SORT_RUNNING_COST, + STR_SORT_BY_RELIABILITY, + STR_ENGINE_SORT_CARGO_CAPACITY, + INVALID_STRING_ID +}; + + +/** +* Draw the purchase info details of an aircraft at a given location. + * @param x,y location where to draw the info + * @param engine_number the engine of which to draw the info of + */ +void DrawAircraftPurchaseInfo(int x, int y, uint w, EngineID engine_number) +{ + const AircraftVehicleInfo *avi = AircraftVehInfo(engine_number); + const Engine *e = GetEngine(engine_number); + CargoID cargo; + YearMonthDay ymd; + ConvertDateToYMD(e->intro_date, &ymd); + + /* Purchase cost - Max speed */ + SetDParam(0, avi->base_cost * (_price.aircraft_base>>3)>>5); + SetDParam(1, avi->max_speed * 128 / 10); + DrawString(x, y, STR_PURCHASE_INFO_COST_SPEED, 0); + y += 10; + + /* Cargo capacity */ + cargo = FindFirstRefittableCargo(engine_number); + if (cargo == CT_INVALID || cargo == CT_PASSENGERS) { + SetDParam(0, avi->passenger_capacity); + SetDParam(1, avi->mail_capacity); + DrawString(x, y, STR_PURCHASE_INFO_AIRCRAFT_CAPACITY, 0); + } else { + /* Note, if the default capacity is selected by the refit capacity + * callback, then the capacity shown is likely to be incorrect. */ + SetDParam(0, cargo); + SetDParam(1, AircraftDefaultCargoCapacity(cargo, engine_number)); + SetDParam(2, STR_9842_REFITTABLE); + DrawString(x, y, STR_PURCHASE_INFO_CAPACITY, 0); + } + y += 10; + + /* Running cost */ + SetDParam(0, avi->running_cost * _price.aircraft_running >> 8); + DrawString(x, y, STR_PURCHASE_INFO_RUNNINGCOST, 0); + y += 10; + + /* Design date - Life length */ + SetDParam(0, ymd.year); + SetDParam(1, e->lifelength); + DrawString(x, y, STR_PURCHASE_INFO_DESIGNED_LIFE, 0); + y += 10; + + /* Reliability */ + SetDParam(0, e->reliability * 100 >> 16); + DrawString(x, y, STR_PURCHASE_INFO_RELIABILITY, 0); + y += 10; + + /* Additional text from NewGRF */ + y += ShowAdditionalText(x, y, w, engine_number); + y += ShowRefitOptionsList(x, y, w, engine_number); +} + +void DrawAircraftImage(const Vehicle *v, int x, int y, VehicleID selection) +{ + PalSpriteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v); + DrawSprite(GetAircraftImage(v, DIR_W) | pal, x + 25, y + 10); + if (v->subtype == 0) { + SpriteID rotor_sprite = GetCustomRotorSprite(v, true); + if (rotor_sprite == 0) rotor_sprite = SPR_ROTOR_STOPPED; + DrawSprite(rotor_sprite, x + 25, y + 5); + } + if (v->index == selection) { + DrawFrameRect(x - 1, y - 1, x + 58, y + 21, 0xF, FR_BORDERONLY); + } +} + +void CcBuildAircraft(bool success, TileIndex tile, uint32 p1, uint32 p2) +{ + if (success) { + const Vehicle *v = GetVehicle(_new_vehicle_id); + + if (v->tile == _backup_orders_tile) { + _backup_orders_tile = 0; + RestoreVehicleOrders(v, _backup_orders_data); + } + ShowAircraftViewWindow(v); + } +} + +static void GenerateBuildAircraftList(Window *w) +{ + EngineID eid, sel_id; + buildvehicle_d *bv = &WP(w, buildvehicle_d); + + EngList_RemoveAll(&bv->eng_list); + + /* Make list of all available planes. + * Also check to see if the previously selected plane is still available, + * and if not, reset selection to INVALID_ENGINE. This could be the case + * when planes become obsolete and are removed */ + sel_id = INVALID_ENGINE; + for (eid = AIRCRAFT_ENGINES_INDEX; eid < AIRCRAFT_ENGINES_INDEX + NUM_AIRCRAFT_ENGINES; eid++) { + if (IsEngineBuildable(eid, VEH_Aircraft, _local_player)) { + const AircraftVehicleInfo *avi = AircraftVehInfo(eid); + switch (bv->filter.acc_planes) { + case HELICOPTERS_ONLY: + if (avi->subtype != 0) continue; // if not helicopter + break; + + case AIRCRAFT_ONLY: + if (avi->subtype == 0) continue; // if helicopter + break; + + case ALL: break; + } + EngList_Add(&bv->eng_list, eid); + + if (eid == bv->sel_engine) sel_id = eid; + } + } + + bv->sel_engine = sel_id; +} + +static void GenerateBuildList(Window *w) +{ + buildvehicle_d *bv = &WP(w, buildvehicle_d); + + switch (bv->vehicle_type) { + case VEH_Aircraft: + GenerateBuildAircraftList(w); + _internal_sort_order = bv->descending_sort_order; + EngList_Sort(&bv->eng_list, _aircraft_sorter[bv->sort_criteria]); + break; + + default: NOT_REACHED(); + } +} + +static void DrawBuildAircraftWindow(Window *w) +{ + const buildvehicle_d *bv = &WP(w, buildvehicle_d); + + SetWindowWidgetDisabledState(w, BUILD_VEHICLE_WIDGET_BUILD, w->window_number == 0); + + SetVScrollCount(w, EngList_Count(&bv->eng_list)); + DrawWindowWidgets(w); + + { + int x = 2; + int y = 27; + EngineID selected_id = bv->sel_engine; + EngineID eid = w->vscroll.pos; + uint16 max = min(w->vscroll.pos + w->vscroll.cap, EngList_Count(&bv->eng_list)); + + for (; eid < max; eid++) { + const EngineID engine = bv->eng_list[eid]; + + DrawString(x + 62, y + 7, GetCustomEngineName(engine), engine == selected_id ? 0xC : 0x10); + DrawAircraftEngine(x + 29, y + 10, engine, GetEnginePalette(engine, _local_player)); + y += 24; + } + + if (selected_id != INVALID_ENGINE) { + const Widget *wi = &w->widget[BUILD_VEHICLE_WIDGET_PANEL]; + DrawAircraftPurchaseInfo(x, wi->top + 1, wi->right - wi->left - 2, selected_id); + } + } + DrawString(85, 15, _aircraft_sort_listing[bv->sort_criteria], 0x10); + DoDrawString(bv->descending_sort_order ? DOWNARROW : UPARROW, 69, 15, 0x10); +} + +static void BuildAircraftClickEvent(Window *w, WindowEvent *e) +{ + buildvehicle_d *bv = &WP(w, buildvehicle_d); + + switch (e->we.click.widget) { + case BUILD_VEHICLE_WIDGET_SORT_ASSENDING_DESCENDING: + bv->descending_sort_order ^= true; + _last_sort_order = bv->descending_sort_order; + GenerateBuildList(w); + SetWindowDirty(w); + break; + + case BUILD_VEHICLE_WIDGET_LIST: { + uint i = (e->we.click.pt.y - 26) / 24 + w->vscroll.pos; + uint num_items = EngList_Count(&bv->eng_list); + bv->sel_engine = (i < num_items) ? bv->eng_list[i] : INVALID_ENGINE; + SetWindowDirty(w); + break; + } + + case BUILD_VEHICLE_WIDGET_SORT_TEXT: case BUILD_VEHICLE_WIDGET_SORT_DROPDOWN:/* Select sorting criteria dropdown menu */ + ShowDropDownMenu(w, _aircraft_sort_listing, bv->sort_criteria, BUILD_VEHICLE_WIDGET_SORT_DROPDOWN, 0, 0); + return; + + case BUILD_VEHICLE_WIDGET_BUILD: { + EngineID sel_eng = bv->sel_engine; + if (sel_eng != INVALID_ENGINE) { + DoCommandP(w->window_number, sel_eng, 0, CcBuildAircraft, CMD_BUILD_AIRCRAFT | CMD_MSG(STR_A008_CAN_T_BUILD_AIRCRAFT)); + } + break; + } + + case BUILD_VEHICLE_WIDGET_RENAME: { + EngineID sel_eng = bv->sel_engine; + if (sel_eng != INVALID_ENGINE) { + bv->rename_engine = sel_eng; + ShowQueryString(GetCustomEngineName(sel_eng), STR_A039_RENAME_AIRCRAFT_TYPE, 31, 160, w, CS_ALPHANUMERAL); + } + break; + } + } +} + +static void NewAircraftWndProc(Window *w, WindowEvent *e) +{ + buildvehicle_d *bv = &WP(w, buildvehicle_d); + + switch (e->event) { + case WE_INVALIDATE_DATA: + GenerateBuildList(w); + break; + + case WE_DESTROY: + EngList_Destroy(&bv->eng_list); + break; + + case WE_PAINT: + DrawBuildAircraftWindow(w); + break; + + case WE_CLICK: + BuildAircraftClickEvent(w, e); + break; + + case WE_ON_EDIT_TEXT: { + if (e->we.edittext.str[0] != '\0') { + _cmd_text = e->we.edittext.str; + DoCommandP(0, bv->rename_engine, 0, NULL, CMD_RENAME_ENGINE | CMD_MSG(STR_A03A_CAN_T_RENAME_AIRCRAFT_TYPE)); + } + break; + } + + case WE_DROPDOWN_SELECT: /* we have selected a dropdown item in the list */ + if (bv->sort_criteria != e->we.dropdown.index) { + bv->sort_criteria = _last_sort_criteria = e->we.dropdown.index; + GenerateBuildList(w); + } + SetWindowDirty(w); + break; + + case WE_RESIZE: + w->vscroll.cap += e->we.sizing.diff.y / 24; + w->widget[BUILD_VEHICLE_WIDGET_LIST].data = (w->vscroll.cap << 8) + 1; + break; + } +} + +static const WindowDesc _build_vehicle_desc = { + WDP_AUTO, WDP_AUTO, 240, 226, + WC_BUILD_VEHICLE,0, + WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_RESIZABLE, + _build_vehicle_widgets, + NewAircraftWndProc +}; + +void ShowBuildVehicleWindow(TileIndex tile, byte type) +{ + buildvehicle_d *bv; + Window *w; + + DeleteWindowById(WC_BUILD_VEHICLE, tile); + w = AllocateWindowDescFront(&_build_vehicle_desc, tile); + if (w == NULL) return; + + w->caption_color = (tile != 0) ? GetTileOwner(tile) : _local_player; + w->resize.step_height = GetVehicleListHeight(type); + w->vscroll.cap = 4; + w->widget[BUILD_VEHICLE_WIDGET_LIST].data = (w->vscroll.cap << 8) + 1; + + bv = &WP(w, buildvehicle_d); + EngList_Create(&bv->eng_list); + bv->sel_engine = INVALID_ENGINE; + bv->sort_criteria = _last_sort_criteria; + bv->descending_sort_order = _last_sort_order; + + bv->vehicle_type = type; + + switch (type) { + case VEH_Aircraft: { + byte acc_planes = (tile == 0) ? ALL : GetAirport(GetStationByTile(tile)->airport_type)->acc_planes; + bv->filter.acc_planes = acc_planes; + break; + } + default: NOT_REACHED(); + } + + GenerateBuildList(w); + /* Select the first plane in the list as default when opening the window */ + if (EngList_Count(&bv->eng_list) > 0) bv->sel_engine = bv->eng_list[0]; +} diff --git a/src/callback_table.c b/src/callback_table.c deleted file mode 100644 --- a/src/callback_table.c +++ /dev/null @@ -1,91 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "callback_table.h" -#include "functions.h" - -// If you add a callback for DoCommandP, also add the callback in here -// see below for the full list! -// If you don't do it, it won't work across the network!! - -/* aircraft_gui.c */ -CommandCallback CcBuildAircraft; -CommandCallback CcCloneAircraft; - -/* airport_gui.c */ -CommandCallback CcBuildAirport; - -/* bridge_gui.c */ -CommandCallback CcBuildBridge; - -/* dock_gui.c */ -CommandCallback CcBuildDocks; -CommandCallback CcBuildCanal; - -/* depot_gui.c */ -CommandCallback CcCloneVehicle; - -/* main_gui.c */ -CommandCallback CcPlaySound10; -CommandCallback CcPlaceSign; -CommandCallback CcTerraform; -CommandCallback CcBuildTown; - -/* rail_gui.c */ -CommandCallback CcPlaySound1E; -CommandCallback CcRailDepot; -CommandCallback CcStation; -CommandCallback CcBuildRailTunnel; - -/* road_gui.c */ -CommandCallback CcPlaySound1D; -CommandCallback CcBuildRoadTunnel; -CommandCallback CcRoadDepot; - -/* roadveh_gui.c */ -CommandCallback CcBuildRoadVeh; -CommandCallback CcCloneRoadVeh; - -/* ship_gui.c */ -CommandCallback CcBuildShip; -CommandCallback CcCloneShip; - -/* train_gui.c */ -CommandCallback CcBuildWagon; -CommandCallback CcBuildLoco; -CommandCallback CcCloneTrain; - -CommandCallback CcAI; - -CommandCallback *_callback_table[] = { - /* 0x00 */ NULL, - /* 0x01 */ CcBuildAircraft, - /* 0x02 */ CcBuildAirport, - /* 0x03 */ CcBuildBridge, - /* 0x04 */ CcBuildCanal, - /* 0x05 */ CcBuildDocks, - /* 0x06 */ CcBuildLoco, - /* 0x07 */ CcBuildRoadVeh, - /* 0x08 */ CcBuildShip, - /* 0x09 */ CcBuildTown, - /* 0x0A */ CcBuildRoadTunnel, - /* 0x0B */ CcBuildRailTunnel, - /* 0x0C */ CcBuildWagon, - /* 0x0D */ CcRoadDepot, - /* 0x0E */ CcRailDepot, - /* 0x0F */ CcPlaceSign, - /* 0x10 */ CcPlaySound10, - /* 0x11 */ CcPlaySound1D, - /* 0x12 */ CcPlaySound1E, - /* 0x13 */ CcStation, - /* 0x14 */ CcTerraform, - /* 0x15 */ CcCloneAircraft, - /* 0x16 */ CcCloneRoadVeh, - /* 0x17 */ CcCloneShip, - /* 0x18 */ CcCloneTrain, - /* 0x19 */ CcAI, - /* 0x1A */ CcCloneVehicle -}; - -const int _callback_table_count = lengthof(_callback_table); diff --git a/src/callback_table.cpp b/src/callback_table.cpp new file mode 100644 --- /dev/null +++ b/src/callback_table.cpp @@ -0,0 +1,91 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "callback_table.h" +#include "functions.h" + +// If you add a callback for DoCommandP, also add the callback in here +// see below for the full list! +// If you don't do it, it won't work across the network!! + +/* aircraft_gui.c */ +CommandCallback CcBuildAircraft; +CommandCallback CcCloneAircraft; + +/* airport_gui.c */ +CommandCallback CcBuildAirport; + +/* bridge_gui.c */ +CommandCallback CcBuildBridge; + +/* dock_gui.c */ +CommandCallback CcBuildDocks; +CommandCallback CcBuildCanal; + +/* depot_gui.c */ +CommandCallback CcCloneVehicle; + +/* main_gui.c */ +CommandCallback CcPlaySound10; +CommandCallback CcPlaceSign; +CommandCallback CcTerraform; +CommandCallback CcBuildTown; + +/* rail_gui.c */ +CommandCallback CcPlaySound1E; +CommandCallback CcRailDepot; +CommandCallback CcStation; +CommandCallback CcBuildRailTunnel; + +/* road_gui.c */ +CommandCallback CcPlaySound1D; +CommandCallback CcBuildRoadTunnel; +CommandCallback CcRoadDepot; + +/* roadveh_gui.c */ +CommandCallback CcBuildRoadVeh; +CommandCallback CcCloneRoadVeh; + +/* ship_gui.c */ +CommandCallback CcBuildShip; +CommandCallback CcCloneShip; + +/* train_gui.c */ +CommandCallback CcBuildWagon; +CommandCallback CcBuildLoco; +CommandCallback CcCloneTrain; + +CommandCallback CcAI; + +CommandCallback *_callback_table[] = { + /* 0x00 */ NULL, + /* 0x01 */ CcBuildAircraft, + /* 0x02 */ CcBuildAirport, + /* 0x03 */ CcBuildBridge, + /* 0x04 */ CcBuildCanal, + /* 0x05 */ CcBuildDocks, + /* 0x06 */ CcBuildLoco, + /* 0x07 */ CcBuildRoadVeh, + /* 0x08 */ CcBuildShip, + /* 0x09 */ CcBuildTown, + /* 0x0A */ CcBuildRoadTunnel, + /* 0x0B */ CcBuildRailTunnel, + /* 0x0C */ CcBuildWagon, + /* 0x0D */ CcRoadDepot, + /* 0x0E */ CcRailDepot, + /* 0x0F */ CcPlaceSign, + /* 0x10 */ CcPlaySound10, + /* 0x11 */ CcPlaySound1D, + /* 0x12 */ CcPlaySound1E, + /* 0x13 */ CcStation, + /* 0x14 */ CcTerraform, + /* 0x15 */ CcCloneAircraft, + /* 0x16 */ CcCloneRoadVeh, + /* 0x17 */ CcCloneShip, + /* 0x18 */ CcCloneTrain, + /* 0x19 */ CcAI, + /* 0x1A */ CcCloneVehicle +}; + +const int _callback_table_count = lengthof(_callback_table); diff --git a/src/clear_cmd.c b/src/clear_cmd.c deleted file mode 100644 --- a/src/clear_cmd.c +++ /dev/null @@ -1,803 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "clear_map.h" -#include "rail_map.h" -#include "table/strings.h" -#include "functions.h" -#include "map.h" -#include "player.h" -#include "tile.h" -#include "viewport.h" -#include "command.h" -#include "tunnel_map.h" -#include "bridge_map.h" -#include "variables.h" -#include "table/sprites.h" -#include "unmovable_map.h" -#include "genworld.h" -#include "industry.h" - -typedef struct TerraformerHeightMod { - TileIndex tile; - byte height; -} TerraformerHeightMod; - -typedef struct TerraformerState { - int height[4]; - uint32 flags; - - int direction; - int modheight_count; - int tile_table_count; - - int32 cost; - - TileIndex *tile_table; - TerraformerHeightMod *modheight; - -} TerraformerState; - -static int TerraformAllowTileProcess(TerraformerState *ts, TileIndex tile) -{ - TileIndex *t; - int count; - - if (TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY()) return -1; - - t = ts->tile_table; - for (count = ts->tile_table_count; count != 0; count--, t++) { - if (*t == tile) return 0; - } - - return 1; -} - -static int TerraformGetHeightOfTile(TerraformerState *ts, TileIndex tile) -{ - TerraformerHeightMod *mod = ts->modheight; - int count; - - for (count = ts->modheight_count; count != 0; count--, mod++) { - if (mod->tile == tile) return mod->height; - } - - return TileHeight(tile); -} - -static void TerraformAddDirtyTile(TerraformerState *ts, TileIndex tile) -{ - int count; - TileIndex *t; - - count = ts->tile_table_count; - - if (count >= 625) return; - - for (t = ts->tile_table; count != 0; count--,t++) { - if (*t == tile) return; - } - - ts->tile_table[ts->tile_table_count++] = tile; -} - -static void TerraformAddDirtyTileAround(TerraformerState *ts, TileIndex tile) -{ - TerraformAddDirtyTile(ts, tile + TileDiffXY( 0, -1)); - TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, -1)); - TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, 0)); - TerraformAddDirtyTile(ts, tile); -} - -static int TerraformProc(TerraformerState *ts, TileIndex tile, int mode) -{ - int r; - int32 ret; - - assert(tile < MapSize()); - - r = TerraformAllowTileProcess(ts, tile); - if (r <= 0) return r; - - if (IsTileType(tile, MP_RAILWAY)) { - static const TrackBits safe_track[] = { TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER, TRACK_BIT_RIGHT }; - static const Slope unsafe_slope[] = { SLOPE_S, SLOPE_W, SLOPE_N, SLOPE_E }; - - Slope tileh; - uint z; - - // Nothing could be built at the steep slope - this avoids a bug - // when you have a single diagonal track in one corner on a - // basement and then you raise/lower the other corner. - tileh = GetTileSlope(tile, &z); - if (tileh == unsafe_slope[mode] || - tileh == ComplementSlope(unsafe_slope[mode])) { - _terraform_err_tile = tile; - _error_message = STR_1008_MUST_REMOVE_RAILROAD_TRACK; - return -1; - } - - // If we have a single diagonal track there, the other side of - // tile can be terraformed. - if (IsPlainRailTile(tile) && GetTrackBits(tile) == safe_track[mode]) { - /* If terraforming downwards prevent damaging a potential tunnel below. - * This check is only necessary for flat tiles, because if the tile is - * non-flat, then the corner opposing the rail is raised. Only this corner - * can be lowered and this is a safe action - */ - if (tileh == SLOPE_FLAT && - ts->direction == -1 && - IsTunnelInWay(tile, z - TILE_HEIGHT)) { - _terraform_err_tile = tile; - _error_message = STR_1002_EXCAVATION_WOULD_DAMAGE; - return -1; - } - return 0; - } - } - - ret = DoCommand(tile, 0,0, ts->flags & ~DC_EXEC, CMD_LANDSCAPE_CLEAR); - - if (CmdFailed(ret)) { - _terraform_err_tile = tile; - return -1; - } - - ts->cost += ret; - - if (ts->tile_table_count >= 625) return -1; - ts->tile_table[ts->tile_table_count++] = tile; - - return 0; -} - -static bool TerraformTileHeight(TerraformerState *ts, TileIndex tile, int height) -{ - int nh; - TerraformerHeightMod *mod; - int count; - - assert(tile < MapSize()); - - if (height < 0) { - _error_message = STR_1003_ALREADY_AT_SEA_LEVEL; - return false; - } - - _error_message = STR_1004_TOO_HIGH; - - if (height > 15) return false; - - nh = TerraformGetHeightOfTile(ts, tile); - if (nh < 0 || height == nh) return false; - - if (TerraformProc(ts, tile, 0) < 0) return false; - if (TerraformProc(ts, tile + TileDiffXY( 0, -1), 1) < 0) return false; - if (TerraformProc(ts, tile + TileDiffXY(-1, -1), 2) < 0) return false; - if (TerraformProc(ts, tile + TileDiffXY(-1, 0), 3) < 0) return false; - - mod = ts->modheight; - count = ts->modheight_count; - - for (;;) { - if (count == 0) { - if (ts->modheight_count >= 576) return false; - ts->modheight_count++; - break; - } - if (mod->tile == tile) break; - mod++; - count--; - } - - mod->tile = tile; - mod->height = (byte)height; - - ts->cost += _price.terraform; - - { - int direction = ts->direction, r; - const TileIndexDiffC *ttm; - - static const TileIndexDiffC _terraform_tilepos[] = { - { 1, 0}, - {-2, 0}, - { 1, 1}, - { 0, -2} - }; - - for (ttm = _terraform_tilepos; ttm != endof(_terraform_tilepos); ttm++) { - tile += ToTileIndexDiff(*ttm); - - r = TerraformGetHeightOfTile(ts, tile); - if (r != height && r-direction != height && r+direction != height) { - if (!TerraformTileHeight(ts, tile, r+direction)) - return false; - } - } - } - - return true; -} - -/** Terraform land - * @param tile tile to terraform - * @param p1 corners to terraform. - * @param p2 direction; eg up or down - */ -int32 CmdTerraformLand(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) -{ - TerraformerState ts; - TileIndex t; - int direction; - - TerraformerHeightMod modheight_data[576]; - TileIndex tile_table_data[625]; - - SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); - - _terraform_err_tile = 0; - - ts.direction = direction = p2 ? 1 : -1; - ts.flags = flags; - ts.modheight_count = ts.tile_table_count = 0; - ts.cost = 0; - ts.modheight = modheight_data; - ts.tile_table = tile_table_data; - - /* Make an extra check for map-bounds cause we add tiles to the originating tile */ - if (tile + TileDiffXY(1, 1) >= MapSize()) return CMD_ERROR; - - if (p1 & 1) { - t = tile + TileDiffXY(1, 0); - if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { - return CMD_ERROR; - } - } - - if (p1 & 2) { - t = tile + TileDiffXY(1, 1); - if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { - return CMD_ERROR; - } - } - - if (p1 & 4) { - t = tile + TileDiffXY(0, 1); - if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { - return CMD_ERROR; - } - } - - if (p1 & 8) { - t = tile + TileDiffXY(0, 0); - if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { - return CMD_ERROR; - } - } - - { - /* Check if tunnel would take damage */ - int count; - TileIndex *ti = ts.tile_table; - - for (count = ts.tile_table_count; count != 0; count--, ti++) { - TileIndex tile = *ti; - - if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) { - return_cmd_error(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST); - } - - if (direction == -1) { - uint z, t; - - z = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 0)); - t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 0)); - if (t <= z) z = t; - t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 1)); - if (t <= z) z = t; - t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 1)); - if (t <= z) z = t; - - if (IsTunnelInWay(tile, z * TILE_HEIGHT)) { - return_cmd_error(STR_1002_EXCAVATION_WOULD_DAMAGE); - } - } - } - } - - if (flags & DC_EXEC) { - /* Clear the landscape at the tiles */ - { - int count; - TileIndex *ti = ts.tile_table; - for (count = ts.tile_table_count; count != 0; count--, ti++) { - DoCommand(*ti, 0, 0, flags, CMD_LANDSCAPE_CLEAR); - } - } - - /* change the height */ - { - int count; - TerraformerHeightMod *mod; - - mod = ts.modheight; - for (count = ts.modheight_count; count != 0; count--, mod++) { - TileIndex til = mod->tile; - - SetTileHeight(til, mod->height); - TerraformAddDirtyTileAround(&ts, til); - } - } - - /* finally mark the dirty tiles dirty */ - { - int count; - TileIndex *ti = ts.tile_table; - for (count = ts.tile_table_count; count != 0; count--, ti++) { - MarkTileDirtyByTile(*ti); - } - } - } - return ts.cost; -} - - -/** Levels a selected (rectangle) area of land - * @param tile end tile of area-drag - * @param p1 start tile of area drag - * @param p2 unused - */ -int32 CmdLevelLand(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) -{ - int size_x, size_y; - int ex; - int ey; - int sx, sy; - uint h, curh; - int32 ret, cost, money; - - if (p1 >= MapSize()) return CMD_ERROR; - - SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); - - // remember level height - h = TileHeight(p1); - - // make sure sx,sy are smaller than ex,ey - ex = TileX(tile); - ey = TileY(tile); - sx = TileX(p1); - sy = TileY(p1); - if (ex < sx) intswap(ex, sx); - if (ey < sy) intswap(ey, sy); - tile = TileXY(sx, sy); - - size_x = ex-sx+1; - size_y = ey-sy+1; - - money = GetAvailableMoneyForCommand(); - cost = 0; - - BEGIN_TILE_LOOP(tile2, size_x, size_y, tile) { - curh = TileHeight(tile2); - while (curh != h) { - ret = DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); - if (CmdFailed(ret)) break; - cost += ret; - - if (flags & DC_EXEC) { - if ((money -= ret) < 0) { - _additional_cash_required = ret; - return cost - ret; - } - DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); - } - - curh += (curh > h) ? -1 : 1; - } - } END_TILE_LOOP(tile2, size_x, size_y, tile) - - return (cost == 0) ? CMD_ERROR : cost; -} - -/** Purchase a land area. Actually you only purchase one tile, so - * the name is a bit confusing ;p - * @param tile the tile the player is purchasing - * @param p1 unused - * @param p2 unused - */ -int32 CmdPurchaseLandArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) -{ - int32 cost; - - SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); - - if (!EnsureNoVehicle(tile)) return CMD_ERROR; - - if (IsOwnedLandTile(tile) && IsTileOwner(tile, _current_player)) { - return_cmd_error(STR_5807_YOU_ALREADY_OWN_IT); - } - - cost = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); - if (CmdFailed(cost)) return CMD_ERROR; - - if (flags & DC_EXEC) { - MakeOwnedLand(tile, _current_player); - MarkTileDirtyByTile(tile); - } - - return cost + _price.purchase_land * 10; -} - - -static int32 ClearTile_Clear(TileIndex tile, byte flags) -{ - static const int32* clear_price_table[] = { - &_price.clear_1, - &_price.purchase_land, - &_price.clear_2, - &_price.clear_3, - &_price.purchase_land, - &_price.purchase_land, - &_price.clear_2, // XXX unused? - }; - int32 price; - - if (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) == 0) { - price = 0; - } else { - price = *clear_price_table[GetClearGround(tile)]; - } - - if (flags & DC_EXEC) DoClearSquare(tile); - - return price; -} - -/** Sell a land area. Actually you only sell one tile, so - * the name is a bit confusing ;p - * @param tile the tile the player is selling - * @param p1 unused - * @param p2 unused - */ -int32 CmdSellLandArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) -{ - SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); - - if (!IsOwnedLandTile(tile)) return CMD_ERROR; - if (!CheckTileOwnership(tile) && _current_player != OWNER_WATER) return CMD_ERROR; - - - if (!EnsureNoVehicle(tile)) return CMD_ERROR; - - if (flags & DC_EXEC) DoClearSquare(tile); - - return - _price.purchase_land * 2; -} - - -#include "table/clear_land.h" - - -void DrawClearLandTile(const TileInfo *ti, byte set) -{ - DrawGroundSprite(SPR_FLAT_BARE_LAND + _tileh_to_sprite[ti->tileh] + set * 19); -} - -void DrawHillyLandTile(const TileInfo *ti) -{ - if (ti->tileh != SLOPE_FLAT) { - DrawGroundSprite(SPR_FLAT_ROUGH_LAND + _tileh_to_sprite[ti->tileh]); - } else { - DrawGroundSprite(_landscape_clear_sprites[GB(ti->x ^ ti->y, 4, 3)]); - } -} - -void DrawClearLandFence(const TileInfo *ti) -{ - byte z = ti->z; - - if (ti->tileh & SLOPE_S) { - z += TILE_HEIGHT; - if (ti->tileh == SLOPE_STEEP_S) z += TILE_HEIGHT; - } - - if (GetFenceSW(ti->tile) != 0) { - DrawGroundSpriteAt(_clear_land_fence_sprites_1[GetFenceSW(ti->tile) - 1] + _fence_mod_by_tileh[ti->tileh], ti->x, ti->y, z); - } - - if (GetFenceSE(ti->tile) != 0) { - DrawGroundSpriteAt(_clear_land_fence_sprites_1[GetFenceSE(ti->tile) - 1] + _fence_mod_by_tileh_2[ti->tileh], ti->x, ti->y, z); - } -} - -static void DrawTile_Clear(TileInfo *ti) -{ - switch (GetClearGround(ti->tile)) { - case CLEAR_GRASS: - DrawClearLandTile(ti, GetClearDensity(ti->tile)); - break; - - case CLEAR_ROUGH: - DrawHillyLandTile(ti); - break; - - case CLEAR_ROCKS: - DrawGroundSprite(SPR_FLAT_ROCKY_LAND_1 + _tileh_to_sprite[ti->tileh]); - break; - - case CLEAR_FIELDS: - DrawGroundSprite(_clear_land_sprites_1[GetFieldType(ti->tile)] + _tileh_to_sprite[ti->tileh]); - break; - - case CLEAR_SNOW: - DrawGroundSprite(_clear_land_sprites_2[GetClearDensity(ti->tile)] + _tileh_to_sprite[ti->tileh]); - break; - - case CLEAR_DESERT: - DrawGroundSprite(_clear_land_sprites_3[GetClearDensity(ti->tile)] + _tileh_to_sprite[ti->tileh]); - break; - } - - DrawClearLandFence(ti); - DrawBridgeMiddle(ti); -} - -static uint GetSlopeZ_Clear(TileIndex tile, uint x, uint y) -{ - uint z; - uint tileh = GetTileSlope(tile, &z); - - return z + GetPartialZ(x & 0xF, y & 0xF, tileh); -} - -static Slope GetSlopeTileh_Clear(TileIndex tile, Slope tileh) -{ - return tileh; -} - -static void GetAcceptedCargo_Clear(TileIndex tile, AcceptedCargo ac) -{ - /* unused */ -} - -static void AnimateTile_Clear(TileIndex tile) -{ - /* unused */ -} - -void TileLoopClearHelper(TileIndex tile) -{ - byte self; - byte neighbour; - TileIndex dirty = INVALID_TILE; - - self = (IsTileType(tile, MP_CLEAR) && IsClearGround(tile, CLEAR_FIELDS)); - - neighbour = (IsTileType(TILE_ADDXY(tile, 1, 0), MP_CLEAR) && IsClearGround(TILE_ADDXY(tile, 1, 0), CLEAR_FIELDS)); - if (GetFenceSW(tile) == 0) { - if (self != neighbour) { - SetFenceSW(tile, 3); - dirty = tile; - } - } else { - if (self == 0 && neighbour == 0) { - SetFenceSW(tile, 0); - dirty = tile; - } - } - - neighbour = (IsTileType(TILE_ADDXY(tile, 0, 1), MP_CLEAR) && IsClearGround(TILE_ADDXY(tile, 0, 1), CLEAR_FIELDS)); - if (GetFenceSE(tile) == 0) { - if (self != neighbour) { - SetFenceSE(tile, 3); - dirty = tile; - } - } else { - if (self == 0 && neighbour == 0) { - SetFenceSE(tile, 0); - dirty = tile; - } - } - - if (dirty != INVALID_TILE) MarkTileDirtyByTile(dirty); -} - - -/* convert into snowy tiles */ -static void TileLoopClearAlps(TileIndex tile) -{ - int k = GetTileZ(tile) - _opt.snow_line + TILE_HEIGHT; - - if (k < 0) { // well below the snow line - if (!IsClearGround(tile, CLEAR_SNOW)) return; - if (GetClearDensity(tile) == 0) SetClearGroundDensity(tile, CLEAR_GRASS, 3); - } else { - if (!IsClearGround(tile, CLEAR_SNOW)) { - SetClearGroundDensity(tile, CLEAR_SNOW, 0); - } else { - uint density = min((uint)k / TILE_HEIGHT, 3); - - if (GetClearDensity(tile) < density) { - AddClearDensity(tile, 1); - } else if (GetClearDensity(tile) > density) { - AddClearDensity(tile, -1); - } else { - return; - } - } - } - - MarkTileDirtyByTile(tile); -} - -static void TileLoopClearDesert(TileIndex tile) -{ - if (IsClearGround(tile, CLEAR_DESERT)) return; - - if (GetTropicZone(tile) == TROPICZONE_DESERT) { - SetClearGroundDensity(tile, CLEAR_DESERT, 3); - } else { - if (GetTropicZone(tile + TileDiffXY( 1, 0)) != TROPICZONE_DESERT && - GetTropicZone(tile + TileDiffXY(-1, 0)) != TROPICZONE_DESERT && - GetTropicZone(tile + TileDiffXY( 0, 1)) != TROPICZONE_DESERT && - GetTropicZone(tile + TileDiffXY( 0, -1)) != TROPICZONE_DESERT) - return; - SetClearGroundDensity(tile, CLEAR_DESERT, 1); - } - - MarkTileDirtyByTile(tile); -} - -static void TileLoop_Clear(TileIndex tile) -{ - TileLoopClearHelper(tile); - - switch (_opt.landscape) { - case LT_DESERT: TileLoopClearDesert(tile); break; - case LT_HILLY: TileLoopClearAlps(tile); break; - } - - switch (GetClearGround(tile)) { - case CLEAR_GRASS: - if (GetClearDensity(tile) == 3) return; - - if (_game_mode != GM_EDITOR) { - if (GetClearCounter(tile) < 7) { - AddClearCounter(tile, 1); - return; - } else { - SetClearCounter(tile, 0); - AddClearDensity(tile, 1); - } - } else { - SetClearGroundDensity(tile, GB(Random(), 0, 8) > 21 ? CLEAR_GRASS : CLEAR_ROUGH, 3); - } - break; - - case CLEAR_FIELDS: { - uint field_type; - - if (_game_mode == GM_EDITOR) return; - - if (GetClearCounter(tile) < 7) { - AddClearCounter(tile, 1); - return; - } else { - SetClearCounter(tile, 0); - } - - if (GetIndustryIndexOfField(tile) == INVALID_INDUSTRY && GetFieldType(tile) >= 7) { - /* This farmfield is no longer farmfield, so make it grass again */ - MakeClear(tile, CLEAR_GRASS, 2); - } else { - field_type = GetFieldType(tile); - field_type = (field_type < 8) ? field_type + 1 : 0; - SetFieldType(tile, field_type); - } - break; - } - - default: - return; - } - - MarkTileDirtyByTile(tile); -} - -void GenerateClearTile(void) -{ - uint i, gi; - TileIndex tile; - - /* add rough tiles */ - i = ScaleByMapSize(GB(Random(), 0, 10) + 0x400); - gi = ScaleByMapSize(GB(Random(), 0, 7) + 0x80); - - SetGeneratingWorldProgress(GWP_ROUGH_ROCKY, gi + i); - do { - IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); - tile = RandomTile(); - if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) SetClearGroundDensity(tile, CLEAR_ROUGH, 3); - } while (--i); - - /* add rocky tiles */ - i = gi; - do { - uint32 r = Random(); - tile = RandomTileSeed(r); - - IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); - if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) { - uint j = GB(r, 16, 4) + 5; - for (;;) { - TileIndex tile_new; - - SetClearGroundDensity(tile, CLEAR_ROCKS, 3); - do { - if (--j == 0) goto get_out; - tile_new = tile + TileOffsByDiagDir(GB(Random(), 0, 2)); - } while (!IsTileType(tile_new, MP_CLEAR) || IsClearGround(tile_new, CLEAR_DESERT)); - tile = tile_new; - } -get_out:; - } - } while (--i); -} - -static void ClickTile_Clear(TileIndex tile) -{ - /* not used */ -} - -static uint32 GetTileTrackStatus_Clear(TileIndex tile, TransportType mode) -{ - return 0; -} - -static const StringID _clear_land_str[] = { - STR_080D_GRASS, - STR_080B_ROUGH_LAND, - STR_080A_ROCKS, - STR_080E_FIELDS, - STR_080F_SNOW_COVERED_LAND, - STR_0810_DESERT -}; - -static void GetTileDesc_Clear(TileIndex tile, TileDesc *td) -{ - if (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) == 0) { - td->str = STR_080C_BARE_LAND; - } else { - td->str = _clear_land_str[GetClearGround(tile)]; - } - td->owner = GetTileOwner(tile); -} - -static void ChangeTileOwner_Clear(TileIndex tile, PlayerID old_player, PlayerID new_player) -{ - return; -} - -void InitializeClearLand(void) -{ - _opt.snow_line = _patches.snow_line_height * TILE_HEIGHT; -} - -const TileTypeProcs _tile_type_clear_procs = { - DrawTile_Clear, /* draw_tile_proc */ - GetSlopeZ_Clear, /* get_slope_z_proc */ - ClearTile_Clear, /* clear_tile_proc */ - GetAcceptedCargo_Clear, /* get_accepted_cargo_proc */ - GetTileDesc_Clear, /* get_tile_desc_proc */ - GetTileTrackStatus_Clear, /* get_tile_track_status_proc */ - ClickTile_Clear, /* click_tile_proc */ - AnimateTile_Clear, /* animate_tile_proc */ - TileLoop_Clear, /* tile_loop_clear */ - ChangeTileOwner_Clear, /* change_tile_owner_clear */ - NULL, /* get_produced_cargo_proc */ - NULL, /* vehicle_enter_tile_proc */ - GetSlopeTileh_Clear, /* get_slope_tileh_proc */ -}; diff --git a/src/clear_cmd.cpp b/src/clear_cmd.cpp new file mode 100644 --- /dev/null +++ b/src/clear_cmd.cpp @@ -0,0 +1,803 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "clear_map.h" +#include "rail_map.h" +#include "table/strings.h" +#include "functions.h" +#include "map.h" +#include "player.h" +#include "tile.h" +#include "viewport.h" +#include "command.h" +#include "tunnel_map.h" +#include "bridge_map.h" +#include "variables.h" +#include "table/sprites.h" +#include "unmovable_map.h" +#include "genworld.h" +#include "industry.h" + +typedef struct TerraformerHeightMod { + TileIndex tile; + byte height; +} TerraformerHeightMod; + +typedef struct TerraformerState { + int height[4]; + uint32 flags; + + int direction; + int modheight_count; + int tile_table_count; + + int32 cost; + + TileIndex *tile_table; + TerraformerHeightMod *modheight; + +} TerraformerState; + +static int TerraformAllowTileProcess(TerraformerState *ts, TileIndex tile) +{ + TileIndex *t; + int count; + + if (TileX(tile) == MapMaxX() || TileY(tile) == MapMaxY()) return -1; + + t = ts->tile_table; + for (count = ts->tile_table_count; count != 0; count--, t++) { + if (*t == tile) return 0; + } + + return 1; +} + +static int TerraformGetHeightOfTile(TerraformerState *ts, TileIndex tile) +{ + TerraformerHeightMod *mod = ts->modheight; + int count; + + for (count = ts->modheight_count; count != 0; count--, mod++) { + if (mod->tile == tile) return mod->height; + } + + return TileHeight(tile); +} + +static void TerraformAddDirtyTile(TerraformerState *ts, TileIndex tile) +{ + int count; + TileIndex *t; + + count = ts->tile_table_count; + + if (count >= 625) return; + + for (t = ts->tile_table; count != 0; count--,t++) { + if (*t == tile) return; + } + + ts->tile_table[ts->tile_table_count++] = tile; +} + +static void TerraformAddDirtyTileAround(TerraformerState *ts, TileIndex tile) +{ + TerraformAddDirtyTile(ts, tile + TileDiffXY( 0, -1)); + TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, -1)); + TerraformAddDirtyTile(ts, tile + TileDiffXY(-1, 0)); + TerraformAddDirtyTile(ts, tile); +} + +static int TerraformProc(TerraformerState *ts, TileIndex tile, int mode) +{ + int r; + int32 ret; + + assert(tile < MapSize()); + + r = TerraformAllowTileProcess(ts, tile); + if (r <= 0) return r; + + if (IsTileType(tile, MP_RAILWAY)) { + static const TrackBits safe_track[] = { TRACK_BIT_LOWER, TRACK_BIT_LEFT, TRACK_BIT_UPPER, TRACK_BIT_RIGHT }; + static const Slope unsafe_slope[] = { SLOPE_S, SLOPE_W, SLOPE_N, SLOPE_E }; + + Slope tileh; + uint z; + + // Nothing could be built at the steep slope - this avoids a bug + // when you have a single diagonal track in one corner on a + // basement and then you raise/lower the other corner. + tileh = GetTileSlope(tile, &z); + if (tileh == unsafe_slope[mode] || + tileh == ComplementSlope(unsafe_slope[mode])) { + _terraform_err_tile = tile; + _error_message = STR_1008_MUST_REMOVE_RAILROAD_TRACK; + return -1; + } + + // If we have a single diagonal track there, the other side of + // tile can be terraformed. + if (IsPlainRailTile(tile) && GetTrackBits(tile) == safe_track[mode]) { + /* If terraforming downwards prevent damaging a potential tunnel below. + * This check is only necessary for flat tiles, because if the tile is + * non-flat, then the corner opposing the rail is raised. Only this corner + * can be lowered and this is a safe action + */ + if (tileh == SLOPE_FLAT && + ts->direction == -1 && + IsTunnelInWay(tile, z - TILE_HEIGHT)) { + _terraform_err_tile = tile; + _error_message = STR_1002_EXCAVATION_WOULD_DAMAGE; + return -1; + } + return 0; + } + } + + ret = DoCommand(tile, 0,0, ts->flags & ~DC_EXEC, CMD_LANDSCAPE_CLEAR); + + if (CmdFailed(ret)) { + _terraform_err_tile = tile; + return -1; + } + + ts->cost += ret; + + if (ts->tile_table_count >= 625) return -1; + ts->tile_table[ts->tile_table_count++] = tile; + + return 0; +} + +static bool TerraformTileHeight(TerraformerState *ts, TileIndex tile, int height) +{ + int nh; + TerraformerHeightMod *mod; + int count; + + assert(tile < MapSize()); + + if (height < 0) { + _error_message = STR_1003_ALREADY_AT_SEA_LEVEL; + return false; + } + + _error_message = STR_1004_TOO_HIGH; + + if (height > 15) return false; + + nh = TerraformGetHeightOfTile(ts, tile); + if (nh < 0 || height == nh) return false; + + if (TerraformProc(ts, tile, 0) < 0) return false; + if (TerraformProc(ts, tile + TileDiffXY( 0, -1), 1) < 0) return false; + if (TerraformProc(ts, tile + TileDiffXY(-1, -1), 2) < 0) return false; + if (TerraformProc(ts, tile + TileDiffXY(-1, 0), 3) < 0) return false; + + mod = ts->modheight; + count = ts->modheight_count; + + for (;;) { + if (count == 0) { + if (ts->modheight_count >= 576) return false; + ts->modheight_count++; + break; + } + if (mod->tile == tile) break; + mod++; + count--; + } + + mod->tile = tile; + mod->height = (byte)height; + + ts->cost += _price.terraform; + + { + int direction = ts->direction, r; + const TileIndexDiffC *ttm; + + static const TileIndexDiffC _terraform_tilepos[] = { + { 1, 0}, + {-2, 0}, + { 1, 1}, + { 0, -2} + }; + + for (ttm = _terraform_tilepos; ttm != endof(_terraform_tilepos); ttm++) { + tile += ToTileIndexDiff(*ttm); + + r = TerraformGetHeightOfTile(ts, tile); + if (r != height && r-direction != height && r+direction != height) { + if (!TerraformTileHeight(ts, tile, r+direction)) + return false; + } + } + } + + return true; +} + +/** Terraform land + * @param tile tile to terraform + * @param p1 corners to terraform. + * @param p2 direction; eg up or down + */ +int32 CmdTerraformLand(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + TerraformerState ts; + TileIndex t; + int direction; + + TerraformerHeightMod modheight_data[576]; + TileIndex tile_table_data[625]; + + SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); + + _terraform_err_tile = 0; + + ts.direction = direction = p2 ? 1 : -1; + ts.flags = flags; + ts.modheight_count = ts.tile_table_count = 0; + ts.cost = 0; + ts.modheight = modheight_data; + ts.tile_table = tile_table_data; + + /* Make an extra check for map-bounds cause we add tiles to the originating tile */ + if (tile + TileDiffXY(1, 1) >= MapSize()) return CMD_ERROR; + + if (p1 & 1) { + t = tile + TileDiffXY(1, 0); + if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { + return CMD_ERROR; + } + } + + if (p1 & 2) { + t = tile + TileDiffXY(1, 1); + if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { + return CMD_ERROR; + } + } + + if (p1 & 4) { + t = tile + TileDiffXY(0, 1); + if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { + return CMD_ERROR; + } + } + + if (p1 & 8) { + t = tile + TileDiffXY(0, 0); + if (!TerraformTileHeight(&ts, t, TileHeight(t) + direction)) { + return CMD_ERROR; + } + } + + { + /* Check if tunnel would take damage */ + int count; + TileIndex *ti = ts.tile_table; + + for (count = ts.tile_table_count; count != 0; count--, ti++) { + TileIndex tile = *ti; + + if (MayHaveBridgeAbove(tile) && IsBridgeAbove(tile)) { + return_cmd_error(STR_5007_MUST_DEMOLISH_BRIDGE_FIRST); + } + + if (direction == -1) { + uint z, t; + + z = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 0)); + t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 0)); + if (t <= z) z = t; + t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(1, 1)); + if (t <= z) z = t; + t = TerraformGetHeightOfTile(&ts, tile + TileDiffXY(0, 1)); + if (t <= z) z = t; + + if (IsTunnelInWay(tile, z * TILE_HEIGHT)) { + return_cmd_error(STR_1002_EXCAVATION_WOULD_DAMAGE); + } + } + } + } + + if (flags & DC_EXEC) { + /* Clear the landscape at the tiles */ + { + int count; + TileIndex *ti = ts.tile_table; + for (count = ts.tile_table_count; count != 0; count--, ti++) { + DoCommand(*ti, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + } + } + + /* change the height */ + { + int count; + TerraformerHeightMod *mod; + + mod = ts.modheight; + for (count = ts.modheight_count; count != 0; count--, mod++) { + TileIndex til = mod->tile; + + SetTileHeight(til, mod->height); + TerraformAddDirtyTileAround(&ts, til); + } + } + + /* finally mark the dirty tiles dirty */ + { + int count; + TileIndex *ti = ts.tile_table; + for (count = ts.tile_table_count; count != 0; count--, ti++) { + MarkTileDirtyByTile(*ti); + } + } + } + return ts.cost; +} + + +/** Levels a selected (rectangle) area of land + * @param tile end tile of area-drag + * @param p1 start tile of area drag + * @param p2 unused + */ +int32 CmdLevelLand(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + int size_x, size_y; + int ex; + int ey; + int sx, sy; + uint h, curh; + int32 ret, cost, money; + + if (p1 >= MapSize()) return CMD_ERROR; + + SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); + + // remember level height + h = TileHeight(p1); + + // make sure sx,sy are smaller than ex,ey + ex = TileX(tile); + ey = TileY(tile); + sx = TileX(p1); + sy = TileY(p1); + if (ex < sx) intswap(ex, sx); + if (ey < sy) intswap(ey, sy); + tile = TileXY(sx, sy); + + size_x = ex-sx+1; + size_y = ey-sy+1; + + money = GetAvailableMoneyForCommand(); + cost = 0; + + BEGIN_TILE_LOOP(tile2, size_x, size_y, tile) { + curh = TileHeight(tile2); + while (curh != h) { + ret = DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags & ~DC_EXEC, CMD_TERRAFORM_LAND); + if (CmdFailed(ret)) break; + cost += ret; + + if (flags & DC_EXEC) { + if ((money -= ret) < 0) { + _additional_cash_required = ret; + return cost - ret; + } + DoCommand(tile2, 8, (curh > h) ? 0 : 1, flags, CMD_TERRAFORM_LAND); + } + + curh += (curh > h) ? -1 : 1; + } + } END_TILE_LOOP(tile2, size_x, size_y, tile) + + return (cost == 0) ? CMD_ERROR : cost; +} + +/** Purchase a land area. Actually you only purchase one tile, so + * the name is a bit confusing ;p + * @param tile the tile the player is purchasing + * @param p1 unused + * @param p2 unused + */ +int32 CmdPurchaseLandArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + int32 cost; + + SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); + + if (!EnsureNoVehicle(tile)) return CMD_ERROR; + + if (IsOwnedLandTile(tile) && IsTileOwner(tile, _current_player)) { + return_cmd_error(STR_5807_YOU_ALREADY_OWN_IT); + } + + cost = DoCommand(tile, 0, 0, flags, CMD_LANDSCAPE_CLEAR); + if (CmdFailed(cost)) return CMD_ERROR; + + if (flags & DC_EXEC) { + MakeOwnedLand(tile, _current_player); + MarkTileDirtyByTile(tile); + } + + return cost + _price.purchase_land * 10; +} + + +static int32 ClearTile_Clear(TileIndex tile, byte flags) +{ + static const int32* clear_price_table[] = { + &_price.clear_1, + &_price.purchase_land, + &_price.clear_2, + &_price.clear_3, + &_price.purchase_land, + &_price.purchase_land, + &_price.clear_2, // XXX unused? + }; + int32 price; + + if (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) == 0) { + price = 0; + } else { + price = *clear_price_table[GetClearGround(tile)]; + } + + if (flags & DC_EXEC) DoClearSquare(tile); + + return price; +} + +/** Sell a land area. Actually you only sell one tile, so + * the name is a bit confusing ;p + * @param tile the tile the player is selling + * @param p1 unused + * @param p2 unused + */ +int32 CmdSellLandArea(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + SET_EXPENSES_TYPE(EXPENSES_CONSTRUCTION); + + if (!IsOwnedLandTile(tile)) return CMD_ERROR; + if (!CheckTileOwnership(tile) && _current_player != OWNER_WATER) return CMD_ERROR; + + + if (!EnsureNoVehicle(tile)) return CMD_ERROR; + + if (flags & DC_EXEC) DoClearSquare(tile); + + return - _price.purchase_land * 2; +} + + +#include "table/clear_land.h" + + +void DrawClearLandTile(const TileInfo *ti, byte set) +{ + DrawGroundSprite(SPR_FLAT_BARE_LAND + _tileh_to_sprite[ti->tileh] + set * 19); +} + +void DrawHillyLandTile(const TileInfo *ti) +{ + if (ti->tileh != SLOPE_FLAT) { + DrawGroundSprite(SPR_FLAT_ROUGH_LAND + _tileh_to_sprite[ti->tileh]); + } else { + DrawGroundSprite(_landscape_clear_sprites[GB(ti->x ^ ti->y, 4, 3)]); + } +} + +void DrawClearLandFence(const TileInfo *ti) +{ + byte z = ti->z; + + if (ti->tileh & SLOPE_S) { + z += TILE_HEIGHT; + if (ti->tileh == SLOPE_STEEP_S) z += TILE_HEIGHT; + } + + if (GetFenceSW(ti->tile) != 0) { + DrawGroundSpriteAt(_clear_land_fence_sprites_1[GetFenceSW(ti->tile) - 1] + _fence_mod_by_tileh[ti->tileh], ti->x, ti->y, z); + } + + if (GetFenceSE(ti->tile) != 0) { + DrawGroundSpriteAt(_clear_land_fence_sprites_1[GetFenceSE(ti->tile) - 1] + _fence_mod_by_tileh_2[ti->tileh], ti->x, ti->y, z); + } +} + +static void DrawTile_Clear(TileInfo *ti) +{ + switch (GetClearGround(ti->tile)) { + case CLEAR_GRASS: + DrawClearLandTile(ti, GetClearDensity(ti->tile)); + break; + + case CLEAR_ROUGH: + DrawHillyLandTile(ti); + break; + + case CLEAR_ROCKS: + DrawGroundSprite(SPR_FLAT_ROCKY_LAND_1 + _tileh_to_sprite[ti->tileh]); + break; + + case CLEAR_FIELDS: + DrawGroundSprite(_clear_land_sprites_1[GetFieldType(ti->tile)] + _tileh_to_sprite[ti->tileh]); + break; + + case CLEAR_SNOW: + DrawGroundSprite(_clear_land_sprites_2[GetClearDensity(ti->tile)] + _tileh_to_sprite[ti->tileh]); + break; + + case CLEAR_DESERT: + DrawGroundSprite(_clear_land_sprites_3[GetClearDensity(ti->tile)] + _tileh_to_sprite[ti->tileh]); + break; + } + + DrawClearLandFence(ti); + DrawBridgeMiddle(ti); +} + +static uint GetSlopeZ_Clear(TileIndex tile, uint x, uint y) +{ + uint z; + uint tileh = GetTileSlope(tile, &z); + + return z + GetPartialZ(x & 0xF, y & 0xF, tileh); +} + +static Slope GetSlopeTileh_Clear(TileIndex tile, Slope tileh) +{ + return tileh; +} + +static void GetAcceptedCargo_Clear(TileIndex tile, AcceptedCargo ac) +{ + /* unused */ +} + +static void AnimateTile_Clear(TileIndex tile) +{ + /* unused */ +} + +void TileLoopClearHelper(TileIndex tile) +{ + byte self; + byte neighbour; + TileIndex dirty = INVALID_TILE; + + self = (IsTileType(tile, MP_CLEAR) && IsClearGround(tile, CLEAR_FIELDS)); + + neighbour = (IsTileType(TILE_ADDXY(tile, 1, 0), MP_CLEAR) && IsClearGround(TILE_ADDXY(tile, 1, 0), CLEAR_FIELDS)); + if (GetFenceSW(tile) == 0) { + if (self != neighbour) { + SetFenceSW(tile, 3); + dirty = tile; + } + } else { + if (self == 0 && neighbour == 0) { + SetFenceSW(tile, 0); + dirty = tile; + } + } + + neighbour = (IsTileType(TILE_ADDXY(tile, 0, 1), MP_CLEAR) && IsClearGround(TILE_ADDXY(tile, 0, 1), CLEAR_FIELDS)); + if (GetFenceSE(tile) == 0) { + if (self != neighbour) { + SetFenceSE(tile, 3); + dirty = tile; + } + } else { + if (self == 0 && neighbour == 0) { + SetFenceSE(tile, 0); + dirty = tile; + } + } + + if (dirty != INVALID_TILE) MarkTileDirtyByTile(dirty); +} + + +/* convert into snowy tiles */ +static void TileLoopClearAlps(TileIndex tile) +{ + int k = GetTileZ(tile) - _opt.snow_line + TILE_HEIGHT; + + if (k < 0) { // well below the snow line + if (!IsClearGround(tile, CLEAR_SNOW)) return; + if (GetClearDensity(tile) == 0) SetClearGroundDensity(tile, CLEAR_GRASS, 3); + } else { + if (!IsClearGround(tile, CLEAR_SNOW)) { + SetClearGroundDensity(tile, CLEAR_SNOW, 0); + } else { + uint density = min((uint)k / TILE_HEIGHT, 3); + + if (GetClearDensity(tile) < density) { + AddClearDensity(tile, 1); + } else if (GetClearDensity(tile) > density) { + AddClearDensity(tile, -1); + } else { + return; + } + } + } + + MarkTileDirtyByTile(tile); +} + +static void TileLoopClearDesert(TileIndex tile) +{ + if (IsClearGround(tile, CLEAR_DESERT)) return; + + if (GetTropicZone(tile) == TROPICZONE_DESERT) { + SetClearGroundDensity(tile, CLEAR_DESERT, 3); + } else { + if (GetTropicZone(tile + TileDiffXY( 1, 0)) != TROPICZONE_DESERT && + GetTropicZone(tile + TileDiffXY(-1, 0)) != TROPICZONE_DESERT && + GetTropicZone(tile + TileDiffXY( 0, 1)) != TROPICZONE_DESERT && + GetTropicZone(tile + TileDiffXY( 0, -1)) != TROPICZONE_DESERT) + return; + SetClearGroundDensity(tile, CLEAR_DESERT, 1); + } + + MarkTileDirtyByTile(tile); +} + +static void TileLoop_Clear(TileIndex tile) +{ + TileLoopClearHelper(tile); + + switch (_opt.landscape) { + case LT_DESERT: TileLoopClearDesert(tile); break; + case LT_HILLY: TileLoopClearAlps(tile); break; + } + + switch (GetClearGround(tile)) { + case CLEAR_GRASS: + if (GetClearDensity(tile) == 3) return; + + if (_game_mode != GM_EDITOR) { + if (GetClearCounter(tile) < 7) { + AddClearCounter(tile, 1); + return; + } else { + SetClearCounter(tile, 0); + AddClearDensity(tile, 1); + } + } else { + SetClearGroundDensity(tile, GB(Random(), 0, 8) > 21 ? CLEAR_GRASS : CLEAR_ROUGH, 3); + } + break; + + case CLEAR_FIELDS: { + uint field_type; + + if (_game_mode == GM_EDITOR) return; + + if (GetClearCounter(tile) < 7) { + AddClearCounter(tile, 1); + return; + } else { + SetClearCounter(tile, 0); + } + + if (GetIndustryIndexOfField(tile) == INVALID_INDUSTRY && GetFieldType(tile) >= 7) { + /* This farmfield is no longer farmfield, so make it grass again */ + MakeClear(tile, CLEAR_GRASS, 2); + } else { + field_type = GetFieldType(tile); + field_type = (field_type < 8) ? field_type + 1 : 0; + SetFieldType(tile, field_type); + } + break; + } + + default: + return; + } + + MarkTileDirtyByTile(tile); +} + +void GenerateClearTile(void) +{ + uint i, gi; + TileIndex tile; + + /* add rough tiles */ + i = ScaleByMapSize(GB(Random(), 0, 10) + 0x400); + gi = ScaleByMapSize(GB(Random(), 0, 7) + 0x80); + + SetGeneratingWorldProgress(GWP_ROUGH_ROCKY, gi + i); + do { + IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); + tile = RandomTile(); + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) SetClearGroundDensity(tile, CLEAR_ROUGH, 3); + } while (--i); + + /* add rocky tiles */ + i = gi; + do { + uint32 r = Random(); + tile = RandomTileSeed(r); + + IncreaseGeneratingWorldProgress(GWP_ROUGH_ROCKY); + if (IsTileType(tile, MP_CLEAR) && !IsClearGround(tile, CLEAR_DESERT)) { + uint j = GB(r, 16, 4) + 5; + for (;;) { + TileIndex tile_new; + + SetClearGroundDensity(tile, CLEAR_ROCKS, 3); + do { + if (--j == 0) goto get_out; + tile_new = tile + TileOffsByDiagDir(GB(Random(), 0, 2)); + } while (!IsTileType(tile_new, MP_CLEAR) || IsClearGround(tile_new, CLEAR_DESERT)); + tile = tile_new; + } +get_out:; + } + } while (--i); +} + +static void ClickTile_Clear(TileIndex tile) +{ + /* not used */ +} + +static uint32 GetTileTrackStatus_Clear(TileIndex tile, TransportType mode) +{ + return 0; +} + +static const StringID _clear_land_str[] = { + STR_080D_GRASS, + STR_080B_ROUGH_LAND, + STR_080A_ROCKS, + STR_080E_FIELDS, + STR_080F_SNOW_COVERED_LAND, + STR_0810_DESERT +}; + +static void GetTileDesc_Clear(TileIndex tile, TileDesc *td) +{ + if (IsClearGround(tile, CLEAR_GRASS) && GetClearDensity(tile) == 0) { + td->str = STR_080C_BARE_LAND; + } else { + td->str = _clear_land_str[GetClearGround(tile)]; + } + td->owner = GetTileOwner(tile); +} + +static void ChangeTileOwner_Clear(TileIndex tile, PlayerID old_player, PlayerID new_player) +{ + return; +} + +void InitializeClearLand(void) +{ + _opt.snow_line = _patches.snow_line_height * TILE_HEIGHT; +} + +const TileTypeProcs _tile_type_clear_procs = { + DrawTile_Clear, /* draw_tile_proc */ + GetSlopeZ_Clear, /* get_slope_z_proc */ + ClearTile_Clear, /* clear_tile_proc */ + GetAcceptedCargo_Clear, /* get_accepted_cargo_proc */ + GetTileDesc_Clear, /* get_tile_desc_proc */ + GetTileTrackStatus_Clear, /* get_tile_track_status_proc */ + ClickTile_Clear, /* click_tile_proc */ + AnimateTile_Clear, /* animate_tile_proc */ + TileLoop_Clear, /* tile_loop_clear */ + ChangeTileOwner_Clear, /* change_tile_owner_clear */ + NULL, /* get_produced_cargo_proc */ + NULL, /* vehicle_enter_tile_proc */ + GetSlopeTileh_Clear, /* get_slope_tileh_proc */ +}; diff --git a/src/command.c b/src/command.c deleted file mode 100644 --- a/src/command.c +++ /dev/null @@ -1,566 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "table/strings.h" -#include "functions.h" -#include "map.h" -#include "gui.h" -#include "command.h" -#include "player.h" -#include "network/network.h" -#include "variables.h" -#include "genworld.h" - -const char* _cmd_text = NULL; - -#define DEF_COMMAND(yyyy) int32 yyyy(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) - -DEF_COMMAND(CmdBuildRailroadTrack); -DEF_COMMAND(CmdRemoveRailroadTrack); -DEF_COMMAND(CmdBuildSingleRail); -DEF_COMMAND(CmdRemoveSingleRail); - -DEF_COMMAND(CmdLandscapeClear); - -DEF_COMMAND(CmdBuildBridge); - -DEF_COMMAND(CmdBuildRailroadStation); -DEF_COMMAND(CmdRemoveFromRailroadStation); -DEF_COMMAND(CmdConvertRail); - -DEF_COMMAND(CmdBuildSingleSignal); -DEF_COMMAND(CmdRemoveSingleSignal); - -DEF_COMMAND(CmdTerraformLand); - -DEF_COMMAND(CmdPurchaseLandArea); -DEF_COMMAND(CmdSellLandArea); - -DEF_COMMAND(CmdBuildTunnel); - -DEF_COMMAND(CmdBuildTrainDepot); -DEF_COMMAND(CmdBuildTrainWaypoint); -DEF_COMMAND(CmdRenameWaypoint); -DEF_COMMAND(CmdRemoveTrainWaypoint); - -DEF_COMMAND(CmdBuildRoadStop); - -DEF_COMMAND(CmdBuildLongRoad); -DEF_COMMAND(CmdRemoveLongRoad); -DEF_COMMAND(CmdBuildRoad); -DEF_COMMAND(CmdRemoveRoad); - -DEF_COMMAND(CmdBuildRoadDepot); - -DEF_COMMAND(CmdBuildAirport); - -DEF_COMMAND(CmdBuildDock); - -DEF_COMMAND(CmdBuildShipDepot); - -DEF_COMMAND(CmdBuildBuoy); - -DEF_COMMAND(CmdPlantTree); - -DEF_COMMAND(CmdBuildRailVehicle); -DEF_COMMAND(CmdMoveRailVehicle); - -DEF_COMMAND(CmdStartStopTrain); - -DEF_COMMAND(CmdSellRailWagon); - -DEF_COMMAND(CmdSendTrainToDepot); -DEF_COMMAND(CmdForceTrainProceed); -DEF_COMMAND(CmdReverseTrainDirection); - -DEF_COMMAND(CmdModifyOrder); -DEF_COMMAND(CmdSkipOrder); -DEF_COMMAND(CmdDeleteOrder); -DEF_COMMAND(CmdInsertOrder); -DEF_COMMAND(CmdChangeServiceInt); -DEF_COMMAND(CmdRestoreOrderIndex); - -DEF_COMMAND(CmdBuildIndustry); - -DEF_COMMAND(CmdBuildCompanyHQ); -DEF_COMMAND(CmdSetPlayerFace); -DEF_COMMAND(CmdSetPlayerColor); - -DEF_COMMAND(CmdIncreaseLoan); -DEF_COMMAND(CmdDecreaseLoan); - -DEF_COMMAND(CmdWantEnginePreview); - -DEF_COMMAND(CmdNameVehicle); -DEF_COMMAND(CmdRenameEngine); - -DEF_COMMAND(CmdChangeCompanyName); -DEF_COMMAND(CmdChangePresidentName); - -DEF_COMMAND(CmdRenameStation); - -DEF_COMMAND(CmdSellAircraft); -DEF_COMMAND(CmdStartStopAircraft); -DEF_COMMAND(CmdBuildAircraft); -DEF_COMMAND(CmdSendAircraftToHangar); -DEF_COMMAND(CmdRefitAircraft); - -DEF_COMMAND(CmdPlaceSign); -DEF_COMMAND(CmdRenameSign); - -DEF_COMMAND(CmdBuildRoadVeh); -DEF_COMMAND(CmdStartStopRoadVeh); -DEF_COMMAND(CmdSellRoadVeh); -DEF_COMMAND(CmdSendRoadVehToDepot); -DEF_COMMAND(CmdTurnRoadVeh); -DEF_COMMAND(CmdRefitRoadVeh); - -DEF_COMMAND(CmdPause); - -DEF_COMMAND(CmdBuyShareInCompany); -DEF_COMMAND(CmdSellShareInCompany); -DEF_COMMAND(CmdBuyCompany); - -DEF_COMMAND(CmdBuildTown); - -DEF_COMMAND(CmdRenameTown); -DEF_COMMAND(CmdDoTownAction); - -DEF_COMMAND(CmdSetRoadDriveSide); - -DEF_COMMAND(CmdChangeDifficultyLevel); -DEF_COMMAND(CmdChangePatchSetting); - -DEF_COMMAND(CmdStartStopShip); -DEF_COMMAND(CmdSellShip); -DEF_COMMAND(CmdBuildShip); -DEF_COMMAND(CmdSendShipToDepot); -DEF_COMMAND(CmdRefitShip); - -DEF_COMMAND(CmdOrderRefit); -DEF_COMMAND(CmdCloneOrder); - -DEF_COMMAND(CmdClearArea); - -DEF_COMMAND(CmdGiveMoney); -DEF_COMMAND(CmdMoneyCheat); -DEF_COMMAND(CmdBuildCanal); -DEF_COMMAND(CmdBuildLock); - -DEF_COMMAND(CmdPlayerCtrl); - -DEF_COMMAND(CmdLevelLand); - -DEF_COMMAND(CmdRefitRailVehicle); - -DEF_COMMAND(CmdBuildSignalTrack); -DEF_COMMAND(CmdRemoveSignalTrack); - -DEF_COMMAND(CmdSetAutoReplace); - -DEF_COMMAND(CmdCloneVehicle); -DEF_COMMAND(CmdMassStartStopVehicle); -DEF_COMMAND(CmdDepotSellAllVehicles); -DEF_COMMAND(CmdDepotMassAutoReplace); - -/* The master command table */ -static const Command _command_proc_table[] = { - {CmdBuildRailroadTrack, 0}, /* 0 */ - {CmdRemoveRailroadTrack, 0}, /* 1 */ - {CmdBuildSingleRail, 0}, /* 2 */ - {CmdRemoveSingleRail, 0}, /* 3 */ - {CmdLandscapeClear, 0}, /* 4 */ - {CmdBuildBridge, 0}, /* 5 */ - {CmdBuildRailroadStation, 0}, /* 6 */ - {CmdBuildTrainDepot, 0}, /* 7 */ - {CmdBuildSingleSignal, 0}, /* 8 */ - {CmdRemoveSingleSignal, 0}, /* 9 */ - {CmdTerraformLand, 0}, /* 10 */ - {CmdPurchaseLandArea, 0}, /* 11 */ - {CmdSellLandArea, 0}, /* 12 */ - {CmdBuildTunnel, 0}, /* 13 */ - {CmdRemoveFromRailroadStation, 0}, /* 14 */ - {CmdConvertRail, 0}, /* 15 */ - {CmdBuildTrainWaypoint, 0}, /* 16 */ - {CmdRenameWaypoint, 0}, /* 17 */ - {CmdRemoveTrainWaypoint, 0}, /* 18 */ - {NULL, 0}, /* 19 */ - {NULL, 0}, /* 20 */ - {CmdBuildRoadStop, 0}, /* 21 */ - {NULL, 0}, /* 22 */ - {CmdBuildLongRoad, 0}, /* 23 */ - {CmdRemoveLongRoad, 0}, /* 24 */ - {CmdBuildRoad, 0}, /* 25 */ - {CmdRemoveRoad, 0}, /* 26 */ - {CmdBuildRoadDepot, 0}, /* 27 */ - {NULL, 0}, /* 28 */ - {CmdBuildAirport, 0}, /* 29 */ - {CmdBuildDock, 0}, /* 30 */ - {CmdBuildShipDepot, 0}, /* 31 */ - {CmdBuildBuoy, 0}, /* 32 */ - {CmdPlantTree, 0}, /* 33 */ - {CmdBuildRailVehicle, 0}, /* 34 */ - {CmdMoveRailVehicle, 0}, /* 35 */ - {CmdStartStopTrain, 0}, /* 36 */ - {NULL, 0}, /* 37 */ - {CmdSellRailWagon, 0}, /* 38 */ - {CmdSendTrainToDepot, 0}, /* 39 */ - {CmdForceTrainProceed, 0}, /* 40 */ - {CmdReverseTrainDirection, 0}, /* 41 */ - - {CmdModifyOrder, 0}, /* 42 */ - {CmdSkipOrder, 0}, /* 43 */ - {CmdDeleteOrder, 0}, /* 44 */ - {CmdInsertOrder, 0}, /* 45 */ - - {CmdChangeServiceInt, 0}, /* 46 */ - - {CmdBuildIndustry, 0}, /* 47 */ - {CmdBuildCompanyHQ, 0}, /* 48 */ - {CmdSetPlayerFace, 0}, /* 49 */ - {CmdSetPlayerColor, 0}, /* 50 */ - - {CmdIncreaseLoan, 0}, /* 51 */ - {CmdDecreaseLoan, 0}, /* 52 */ - - {CmdWantEnginePreview, 0}, /* 53 */ - - {CmdNameVehicle, 0}, /* 54 */ - {CmdRenameEngine, 0}, /* 55 */ - - {CmdChangeCompanyName, 0}, /* 56 */ - {CmdChangePresidentName, 0}, /* 57 */ - - {CmdRenameStation, 0}, /* 58 */ - - {CmdSellAircraft, 0}, /* 59 */ - {CmdStartStopAircraft, 0}, /* 60 */ - - {CmdBuildAircraft, 0}, /* 61 */ - {CmdSendAircraftToHangar, 0}, /* 62 */ - {NULL, 0}, /* 63 */ - {CmdRefitAircraft, 0}, /* 64 */ - - {CmdPlaceSign, 0}, /* 65 */ - {CmdRenameSign, 0}, /* 66 */ - - {CmdBuildRoadVeh, 0}, /* 67 */ - {CmdStartStopRoadVeh, 0}, /* 68 */ - {CmdSellRoadVeh, 0}, /* 69 */ - {CmdSendRoadVehToDepot, 0}, /* 70 */ - {CmdTurnRoadVeh, 0}, /* 71 */ - {CmdRefitRoadVeh, 0}, /* 72 */ - - {CmdPause, CMD_SERVER}, /* 73 */ - - {CmdBuyShareInCompany, 0}, /* 74 */ - {CmdSellShareInCompany, 0}, /* 75 */ - {CmdBuyCompany, 0}, /* 76 */ - - {CmdBuildTown, CMD_OFFLINE}, /* 77 */ - {NULL, 0}, /* 78 */ - {NULL, 0}, /* 79 */ - {CmdRenameTown, CMD_SERVER}, /* 80 */ - {CmdDoTownAction, 0}, /* 81 */ - - {CmdSetRoadDriveSide, CMD_SERVER}, /* 82 */ - {NULL, 0}, /* 83 */ - {NULL, 0}, /* 84 */ - {CmdChangeDifficultyLevel, CMD_SERVER}, /* 85 */ - - {CmdStartStopShip, 0}, /* 86 */ - {CmdSellShip, 0}, /* 87 */ - {CmdBuildShip, 0}, /* 88 */ - {CmdSendShipToDepot, 0}, /* 89 */ - {NULL, 0}, /* 90 */ - {CmdRefitShip, 0}, /* 91 */ - - {NULL, 0}, /* 92 */ - {NULL, 0}, /* 93 */ - {NULL, 0}, /* 94 */ - {NULL, 0}, /* 95 */ - {NULL, 0}, /* 96 */ - {NULL, 0}, /* 97 */ - - {CmdOrderRefit, 0}, /* 98 */ - {CmdCloneOrder, 0}, /* 99 */ - - {CmdClearArea, 0}, /* 100 */ - {NULL, 0}, /* 101 */ - - {CmdMoneyCheat, CMD_OFFLINE}, /* 102 */ - {CmdBuildCanal, 0}, /* 103 */ - {CmdPlayerCtrl, 0}, /* 104 */ - - {CmdLevelLand, 0}, /* 105 */ - - {CmdRefitRailVehicle, 0}, /* 106 */ - {CmdRestoreOrderIndex, 0}, /* 107 */ - {CmdBuildLock, 0}, /* 108 */ - {NULL, 0}, /* 109 */ - {CmdBuildSignalTrack, 0}, /* 110 */ - {CmdRemoveSignalTrack, 0}, /* 111 */ - {NULL, 0}, /* 112 */ - {CmdGiveMoney, 0}, /* 113 */ - {CmdChangePatchSetting, CMD_SERVER}, /* 114 */ - {CmdSetAutoReplace, 0}, /* 115 */ - {CmdCloneVehicle, 0}, /* 116 */ - {CmdMassStartStopVehicle, 0}, /* 117 */ - {CmdDepotSellAllVehicles, 0}, /* 118 */ - {CmdDepotMassAutoReplace, 0}, /* 119 */ -}; - -/* This function range-checks a cmd, and checks if the cmd is not NULL */ -bool IsValidCommand(uint cmd) -{ - cmd &= 0xFF; - - return - cmd < lengthof(_command_proc_table) && - _command_proc_table[cmd].proc != NULL; -} - -byte GetCommandFlags(uint cmd) -{ - return _command_proc_table[cmd & 0xFF].flags; -} - - -static int _docommand_recursive; - -int32 DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) -{ - int32 res; - CommandProc *proc; - - /* Do not even think about executing out-of-bounds tile-commands */ - if (tile >= MapSize()) { - _cmd_text = NULL; - return CMD_ERROR; - } - - proc = _command_proc_table[procc].proc; - - if (_docommand_recursive == 0) _error_message = INVALID_STRING_ID; - - _docommand_recursive++; - - // only execute the test call if it's toplevel, or we're not execing. - if (_docommand_recursive == 1 || !(flags & DC_EXEC) || (flags & DC_FORCETEST) ) { - res = proc(tile, flags & ~DC_EXEC, p1, p2); - if (CmdFailed(res)) { - if (res & 0xFFFF) _error_message = res & 0xFFFF; - goto error; - } - - if (_docommand_recursive == 1 && - !(flags & DC_QUERY_COST) && - res != 0 && - !CheckPlayerHasMoney(res)) { - goto error; - } - - if (!(flags & DC_EXEC)) { - _docommand_recursive--; - _cmd_text = NULL; - return res; - } - } - - /* Execute the command here. All cost-relevant functions set the expenses type - * themselves with "SET_EXPENSES_TYPE(...);" at the beginning of the function */ - res = proc(tile, flags, p1, p2); - if (CmdFailed(res)) { - if (res & 0xFFFF) _error_message = res & 0xFFFF; -error: - _docommand_recursive--; - _cmd_text = NULL; - return CMD_ERROR; - } - - // if toplevel, subtract the money. - if (--_docommand_recursive == 0) { - SubtractMoneyFromPlayer(res); - // XXX - Old AI hack which doesn't use DoCommandDP; update last build coord of player - if (tile != 0 && IsValidPlayer(_current_player)) { - GetPlayer(_current_player)->last_build_coordinate = tile; - } - } - - _cmd_text = NULL; - return res; -} - -int32 GetAvailableMoneyForCommand(void) -{ - PlayerID pid = _current_player; - if (!IsValidPlayer(pid)) return 0x7FFFFFFF; // max int - return GetPlayer(pid)->player_money; -} - -// toplevel network safe docommand function for the current player. must not be called recursively. -// the callback is called when the command succeeded or failed. -bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback, uint32 cmd) -{ - int32 res = 0,res2; - CommandProc *proc; - uint32 flags; - bool notest; - StringID error_part1; - - int x = TileX(tile) * TILE_SIZE; - int y = TileY(tile) * TILE_SIZE; - - /* Do not even think about executing out-of-bounds tile-commands */ - if (tile >= MapSize()) { - _cmd_text = NULL; - return false; - } - - assert(_docommand_recursive == 0); - - _error_message = INVALID_STRING_ID; - error_part1 = GB(cmd, 16, 16); - _additional_cash_required = 0; - - /** Spectator has no rights except for the (dedicated) server which - * is/can be a spectator but as the server it can do anything */ - if (_current_player == PLAYER_SPECTATOR && !_network_server) { - ShowErrorMessage(_error_message, error_part1, x, y); - _cmd_text = NULL; - return false; - } - - flags = 0; - if (cmd & CMD_AUTO) flags |= DC_AUTO; - if (cmd & CMD_NO_WATER) flags |= DC_NO_WATER; - - // get pointer to command handler - assert((cmd & 0xFF) < lengthof(_command_proc_table)); - proc = _command_proc_table[cmd & 0xFF].proc; - if (proc == NULL) { - _cmd_text = NULL; - return false; - } - - // Some commands have a different output in dryrun than the realrun - // e.g.: if you demolish a whole town, the dryrun would say okay. - // but by really destroying, your rating drops and at a certain point - // it will fail. so res and res2 are different - // CMD_REMOVE_ROAD: This command has special local authority - // restrictions which may cause the test run to fail (the previous - // road fragments still stay there and the town won't let you - // disconnect the road system), but the exec will succeed and this - // fact will trigger an assertion failure. --pasky - notest = - (cmd & 0xFF) == CMD_CLEAR_AREA || - (cmd & 0xFF) == CMD_CONVERT_RAIL || - (cmd & 0xFF) == CMD_LEVEL_LAND || - (cmd & 0xFF) == CMD_REMOVE_ROAD || - (cmd & 0xFF) == CMD_REMOVE_LONG_ROAD; - - _docommand_recursive = 1; - - // cost estimation only? - if (!IsGeneratingWorld() && - _shift_pressed && - IsLocalPlayer() && - !(cmd & (CMD_NETWORK_COMMAND | CMD_SHOW_NO_ERROR)) && - (cmd & 0xFF) != CMD_PAUSE) { - // estimate the cost. - res = proc(tile, flags, p1, p2); - if (CmdFailed(res)) { - if (res & 0xFFFF) _error_message = res & 0xFFFF; - ShowErrorMessage(_error_message, error_part1, x, y); - } else { - ShowEstimatedCostOrIncome(res, x, y); - } - - _docommand_recursive = 0; - _cmd_text = NULL; - return false; - } - - - if (!((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { - // first test if the command can be executed. - res = proc(tile, flags, p1, p2); - if (CmdFailed(res)) { - if (res & 0xFFFF) _error_message = res & 0xFFFF; - goto show_error; - } - // no money? Only check if notest is off - if (!notest && res != 0 && !CheckPlayerHasMoney(res)) goto show_error; - } - -#ifdef ENABLE_NETWORK - /** If we are in network, and the command is not from the network - * send it to the command-queue and abort execution - * If we are a dedicated server temporarily switch local player, otherwise - * the other parties won't be able to execute our command and will desync. - * We also need to do this if the server's company has gone bankrupt - * @todo Rewrite (dedicated) server to something more than a dirty hack! - */ - if (_networking && !(cmd & CMD_NETWORK_COMMAND)) { - PlayerID pbck = _local_player; - if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = 0; - NetworkSend_Command(tile, p1, p2, cmd, callback); - if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = pbck; - _docommand_recursive = 0; - _cmd_text = NULL; - return true; - } -#endif /* ENABLE_NETWORK */ - - // update last build coordinate of player. - if (tile != 0 && IsValidPlayer(_current_player)) { - GetPlayer(_current_player)->last_build_coordinate = tile; - } - - /* Actually try and execute the command. If no cost-type is given - * use the construction one */ - _yearly_expenses_type = EXPENSES_CONSTRUCTION; - res2 = proc(tile, flags | DC_EXEC, p1, p2); - - // If notest is on, it means the result of the test can be different than - // the real command.. so ignore the test - if (!notest && !((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { - assert(res == res2); // sanity check - } else { - if (CmdFailed(res2)) { - if (res2 & 0xFFFF) _error_message = res2 & 0xFFFF; - goto show_error; - } - } - - SubtractMoneyFromPlayer(res2); - - if (IsLocalPlayer() && _game_mode != GM_EDITOR) { - if (res2 != 0) ShowCostOrIncomeAnimation(x, y, GetSlopeZ(x, y), res2); - if (_additional_cash_required) { - SetDParam(0, _additional_cash_required); - ShowErrorMessage(STR_0003_NOT_ENOUGH_CASH_REQUIRES, error_part1, x,y); - if (res2 == 0) goto callb_err; - } - } - - _docommand_recursive = 0; - - if (callback) callback(true, tile, p1, p2); - _cmd_text = NULL; - return true; - -show_error: - // show error message if the command fails? - if (IsLocalPlayer() && error_part1 != 0) { - ShowErrorMessage(_error_message, error_part1, x,y); - } - -callb_err: - _docommand_recursive = 0; - - if (callback) callback(false, tile, p1, p2); - _cmd_text = NULL; - return false; -} diff --git a/src/command.cpp b/src/command.cpp new file mode 100644 --- /dev/null +++ b/src/command.cpp @@ -0,0 +1,566 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "table/strings.h" +#include "functions.h" +#include "map.h" +#include "gui.h" +#include "command.h" +#include "player.h" +#include "network/network.h" +#include "variables.h" +#include "genworld.h" + +const char* _cmd_text = NULL; + +#define DEF_COMMAND(yyyy) int32 yyyy(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) + +DEF_COMMAND(CmdBuildRailroadTrack); +DEF_COMMAND(CmdRemoveRailroadTrack); +DEF_COMMAND(CmdBuildSingleRail); +DEF_COMMAND(CmdRemoveSingleRail); + +DEF_COMMAND(CmdLandscapeClear); + +DEF_COMMAND(CmdBuildBridge); + +DEF_COMMAND(CmdBuildRailroadStation); +DEF_COMMAND(CmdRemoveFromRailroadStation); +DEF_COMMAND(CmdConvertRail); + +DEF_COMMAND(CmdBuildSingleSignal); +DEF_COMMAND(CmdRemoveSingleSignal); + +DEF_COMMAND(CmdTerraformLand); + +DEF_COMMAND(CmdPurchaseLandArea); +DEF_COMMAND(CmdSellLandArea); + +DEF_COMMAND(CmdBuildTunnel); + +DEF_COMMAND(CmdBuildTrainDepot); +DEF_COMMAND(CmdBuildTrainWaypoint); +DEF_COMMAND(CmdRenameWaypoint); +DEF_COMMAND(CmdRemoveTrainWaypoint); + +DEF_COMMAND(CmdBuildRoadStop); + +DEF_COMMAND(CmdBuildLongRoad); +DEF_COMMAND(CmdRemoveLongRoad); +DEF_COMMAND(CmdBuildRoad); +DEF_COMMAND(CmdRemoveRoad); + +DEF_COMMAND(CmdBuildRoadDepot); + +DEF_COMMAND(CmdBuildAirport); + +DEF_COMMAND(CmdBuildDock); + +DEF_COMMAND(CmdBuildShipDepot); + +DEF_COMMAND(CmdBuildBuoy); + +DEF_COMMAND(CmdPlantTree); + +DEF_COMMAND(CmdBuildRailVehicle); +DEF_COMMAND(CmdMoveRailVehicle); + +DEF_COMMAND(CmdStartStopTrain); + +DEF_COMMAND(CmdSellRailWagon); + +DEF_COMMAND(CmdSendTrainToDepot); +DEF_COMMAND(CmdForceTrainProceed); +DEF_COMMAND(CmdReverseTrainDirection); + +DEF_COMMAND(CmdModifyOrder); +DEF_COMMAND(CmdSkipOrder); +DEF_COMMAND(CmdDeleteOrder); +DEF_COMMAND(CmdInsertOrder); +DEF_COMMAND(CmdChangeServiceInt); +DEF_COMMAND(CmdRestoreOrderIndex); + +DEF_COMMAND(CmdBuildIndustry); + +DEF_COMMAND(CmdBuildCompanyHQ); +DEF_COMMAND(CmdSetPlayerFace); +DEF_COMMAND(CmdSetPlayerColor); + +DEF_COMMAND(CmdIncreaseLoan); +DEF_COMMAND(CmdDecreaseLoan); + +DEF_COMMAND(CmdWantEnginePreview); + +DEF_COMMAND(CmdNameVehicle); +DEF_COMMAND(CmdRenameEngine); + +DEF_COMMAND(CmdChangeCompanyName); +DEF_COMMAND(CmdChangePresidentName); + +DEF_COMMAND(CmdRenameStation); + +DEF_COMMAND(CmdSellAircraft); +DEF_COMMAND(CmdStartStopAircraft); +DEF_COMMAND(CmdBuildAircraft); +DEF_COMMAND(CmdSendAircraftToHangar); +DEF_COMMAND(CmdRefitAircraft); + +DEF_COMMAND(CmdPlaceSign); +DEF_COMMAND(CmdRenameSign); + +DEF_COMMAND(CmdBuildRoadVeh); +DEF_COMMAND(CmdStartStopRoadVeh); +DEF_COMMAND(CmdSellRoadVeh); +DEF_COMMAND(CmdSendRoadVehToDepot); +DEF_COMMAND(CmdTurnRoadVeh); +DEF_COMMAND(CmdRefitRoadVeh); + +DEF_COMMAND(CmdPause); + +DEF_COMMAND(CmdBuyShareInCompany); +DEF_COMMAND(CmdSellShareInCompany); +DEF_COMMAND(CmdBuyCompany); + +DEF_COMMAND(CmdBuildTown); + +DEF_COMMAND(CmdRenameTown); +DEF_COMMAND(CmdDoTownAction); + +DEF_COMMAND(CmdSetRoadDriveSide); + +DEF_COMMAND(CmdChangeDifficultyLevel); +DEF_COMMAND(CmdChangePatchSetting); + +DEF_COMMAND(CmdStartStopShip); +DEF_COMMAND(CmdSellShip); +DEF_COMMAND(CmdBuildShip); +DEF_COMMAND(CmdSendShipToDepot); +DEF_COMMAND(CmdRefitShip); + +DEF_COMMAND(CmdOrderRefit); +DEF_COMMAND(CmdCloneOrder); + +DEF_COMMAND(CmdClearArea); + +DEF_COMMAND(CmdGiveMoney); +DEF_COMMAND(CmdMoneyCheat); +DEF_COMMAND(CmdBuildCanal); +DEF_COMMAND(CmdBuildLock); + +DEF_COMMAND(CmdPlayerCtrl); + +DEF_COMMAND(CmdLevelLand); + +DEF_COMMAND(CmdRefitRailVehicle); + +DEF_COMMAND(CmdBuildSignalTrack); +DEF_COMMAND(CmdRemoveSignalTrack); + +DEF_COMMAND(CmdSetAutoReplace); + +DEF_COMMAND(CmdCloneVehicle); +DEF_COMMAND(CmdMassStartStopVehicle); +DEF_COMMAND(CmdDepotSellAllVehicles); +DEF_COMMAND(CmdDepotMassAutoReplace); + +/* The master command table */ +static const Command _command_proc_table[] = { + {CmdBuildRailroadTrack, 0}, /* 0 */ + {CmdRemoveRailroadTrack, 0}, /* 1 */ + {CmdBuildSingleRail, 0}, /* 2 */ + {CmdRemoveSingleRail, 0}, /* 3 */ + {CmdLandscapeClear, 0}, /* 4 */ + {CmdBuildBridge, 0}, /* 5 */ + {CmdBuildRailroadStation, 0}, /* 6 */ + {CmdBuildTrainDepot, 0}, /* 7 */ + {CmdBuildSingleSignal, 0}, /* 8 */ + {CmdRemoveSingleSignal, 0}, /* 9 */ + {CmdTerraformLand, 0}, /* 10 */ + {CmdPurchaseLandArea, 0}, /* 11 */ + {CmdSellLandArea, 0}, /* 12 */ + {CmdBuildTunnel, 0}, /* 13 */ + {CmdRemoveFromRailroadStation, 0}, /* 14 */ + {CmdConvertRail, 0}, /* 15 */ + {CmdBuildTrainWaypoint, 0}, /* 16 */ + {CmdRenameWaypoint, 0}, /* 17 */ + {CmdRemoveTrainWaypoint, 0}, /* 18 */ + {NULL, 0}, /* 19 */ + {NULL, 0}, /* 20 */ + {CmdBuildRoadStop, 0}, /* 21 */ + {NULL, 0}, /* 22 */ + {CmdBuildLongRoad, 0}, /* 23 */ + {CmdRemoveLongRoad, 0}, /* 24 */ + {CmdBuildRoad, 0}, /* 25 */ + {CmdRemoveRoad, 0}, /* 26 */ + {CmdBuildRoadDepot, 0}, /* 27 */ + {NULL, 0}, /* 28 */ + {CmdBuildAirport, 0}, /* 29 */ + {CmdBuildDock, 0}, /* 30 */ + {CmdBuildShipDepot, 0}, /* 31 */ + {CmdBuildBuoy, 0}, /* 32 */ + {CmdPlantTree, 0}, /* 33 */ + {CmdBuildRailVehicle, 0}, /* 34 */ + {CmdMoveRailVehicle, 0}, /* 35 */ + {CmdStartStopTrain, 0}, /* 36 */ + {NULL, 0}, /* 37 */ + {CmdSellRailWagon, 0}, /* 38 */ + {CmdSendTrainToDepot, 0}, /* 39 */ + {CmdForceTrainProceed, 0}, /* 40 */ + {CmdReverseTrainDirection, 0}, /* 41 */ + + {CmdModifyOrder, 0}, /* 42 */ + {CmdSkipOrder, 0}, /* 43 */ + {CmdDeleteOrder, 0}, /* 44 */ + {CmdInsertOrder, 0}, /* 45 */ + + {CmdChangeServiceInt, 0}, /* 46 */ + + {CmdBuildIndustry, 0}, /* 47 */ + {CmdBuildCompanyHQ, 0}, /* 48 */ + {CmdSetPlayerFace, 0}, /* 49 */ + {CmdSetPlayerColor, 0}, /* 50 */ + + {CmdIncreaseLoan, 0}, /* 51 */ + {CmdDecreaseLoan, 0}, /* 52 */ + + {CmdWantEnginePreview, 0}, /* 53 */ + + {CmdNameVehicle, 0}, /* 54 */ + {CmdRenameEngine, 0}, /* 55 */ + + {CmdChangeCompanyName, 0}, /* 56 */ + {CmdChangePresidentName, 0}, /* 57 */ + + {CmdRenameStation, 0}, /* 58 */ + + {CmdSellAircraft, 0}, /* 59 */ + {CmdStartStopAircraft, 0}, /* 60 */ + + {CmdBuildAircraft, 0}, /* 61 */ + {CmdSendAircraftToHangar, 0}, /* 62 */ + {NULL, 0}, /* 63 */ + {CmdRefitAircraft, 0}, /* 64 */ + + {CmdPlaceSign, 0}, /* 65 */ + {CmdRenameSign, 0}, /* 66 */ + + {CmdBuildRoadVeh, 0}, /* 67 */ + {CmdStartStopRoadVeh, 0}, /* 68 */ + {CmdSellRoadVeh, 0}, /* 69 */ + {CmdSendRoadVehToDepot, 0}, /* 70 */ + {CmdTurnRoadVeh, 0}, /* 71 */ + {CmdRefitRoadVeh, 0}, /* 72 */ + + {CmdPause, CMD_SERVER}, /* 73 */ + + {CmdBuyShareInCompany, 0}, /* 74 */ + {CmdSellShareInCompany, 0}, /* 75 */ + {CmdBuyCompany, 0}, /* 76 */ + + {CmdBuildTown, CMD_OFFLINE}, /* 77 */ + {NULL, 0}, /* 78 */ + {NULL, 0}, /* 79 */ + {CmdRenameTown, CMD_SERVER}, /* 80 */ + {CmdDoTownAction, 0}, /* 81 */ + + {CmdSetRoadDriveSide, CMD_SERVER}, /* 82 */ + {NULL, 0}, /* 83 */ + {NULL, 0}, /* 84 */ + {CmdChangeDifficultyLevel, CMD_SERVER}, /* 85 */ + + {CmdStartStopShip, 0}, /* 86 */ + {CmdSellShip, 0}, /* 87 */ + {CmdBuildShip, 0}, /* 88 */ + {CmdSendShipToDepot, 0}, /* 89 */ + {NULL, 0}, /* 90 */ + {CmdRefitShip, 0}, /* 91 */ + + {NULL, 0}, /* 92 */ + {NULL, 0}, /* 93 */ + {NULL, 0}, /* 94 */ + {NULL, 0}, /* 95 */ + {NULL, 0}, /* 96 */ + {NULL, 0}, /* 97 */ + + {CmdOrderRefit, 0}, /* 98 */ + {CmdCloneOrder, 0}, /* 99 */ + + {CmdClearArea, 0}, /* 100 */ + {NULL, 0}, /* 101 */ + + {CmdMoneyCheat, CMD_OFFLINE}, /* 102 */ + {CmdBuildCanal, 0}, /* 103 */ + {CmdPlayerCtrl, 0}, /* 104 */ + + {CmdLevelLand, 0}, /* 105 */ + + {CmdRefitRailVehicle, 0}, /* 106 */ + {CmdRestoreOrderIndex, 0}, /* 107 */ + {CmdBuildLock, 0}, /* 108 */ + {NULL, 0}, /* 109 */ + {CmdBuildSignalTrack, 0}, /* 110 */ + {CmdRemoveSignalTrack, 0}, /* 111 */ + {NULL, 0}, /* 112 */ + {CmdGiveMoney, 0}, /* 113 */ + {CmdChangePatchSetting, CMD_SERVER}, /* 114 */ + {CmdSetAutoReplace, 0}, /* 115 */ + {CmdCloneVehicle, 0}, /* 116 */ + {CmdMassStartStopVehicle, 0}, /* 117 */ + {CmdDepotSellAllVehicles, 0}, /* 118 */ + {CmdDepotMassAutoReplace, 0}, /* 119 */ +}; + +/* This function range-checks a cmd, and checks if the cmd is not NULL */ +bool IsValidCommand(uint cmd) +{ + cmd &= 0xFF; + + return + cmd < lengthof(_command_proc_table) && + _command_proc_table[cmd].proc != NULL; +} + +byte GetCommandFlags(uint cmd) +{ + return _command_proc_table[cmd & 0xFF].flags; +} + + +static int _docommand_recursive; + +int32 DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint32 flags, uint procc) +{ + int32 res; + CommandProc *proc; + + /* Do not even think about executing out-of-bounds tile-commands */ + if (tile >= MapSize()) { + _cmd_text = NULL; + return CMD_ERROR; + } + + proc = _command_proc_table[procc].proc; + + if (_docommand_recursive == 0) _error_message = INVALID_STRING_ID; + + _docommand_recursive++; + + // only execute the test call if it's toplevel, or we're not execing. + if (_docommand_recursive == 1 || !(flags & DC_EXEC) || (flags & DC_FORCETEST) ) { + res = proc(tile, flags & ~DC_EXEC, p1, p2); + if (CmdFailed(res)) { + if (res & 0xFFFF) _error_message = res & 0xFFFF; + goto error; + } + + if (_docommand_recursive == 1 && + !(flags & DC_QUERY_COST) && + res != 0 && + !CheckPlayerHasMoney(res)) { + goto error; + } + + if (!(flags & DC_EXEC)) { + _docommand_recursive--; + _cmd_text = NULL; + return res; + } + } + + /* Execute the command here. All cost-relevant functions set the expenses type + * themselves with "SET_EXPENSES_TYPE(...);" at the beginning of the function */ + res = proc(tile, flags, p1, p2); + if (CmdFailed(res)) { + if (res & 0xFFFF) _error_message = res & 0xFFFF; +error: + _docommand_recursive--; + _cmd_text = NULL; + return CMD_ERROR; + } + + // if toplevel, subtract the money. + if (--_docommand_recursive == 0) { + SubtractMoneyFromPlayer(res); + // XXX - Old AI hack which doesn't use DoCommandDP; update last build coord of player + if (tile != 0 && IsValidPlayer(_current_player)) { + GetPlayer(_current_player)->last_build_coordinate = tile; + } + } + + _cmd_text = NULL; + return res; +} + +int32 GetAvailableMoneyForCommand(void) +{ + PlayerID pid = _current_player; + if (!IsValidPlayer(pid)) return 0x7FFFFFFF; // max int + return GetPlayer(pid)->player_money; +} + +// toplevel network safe docommand function for the current player. must not be called recursively. +// the callback is called when the command succeeded or failed. +bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback, uint32 cmd) +{ + int32 res = 0,res2; + CommandProc *proc; + uint32 flags; + bool notest; + StringID error_part1; + + int x = TileX(tile) * TILE_SIZE; + int y = TileY(tile) * TILE_SIZE; + + /* Do not even think about executing out-of-bounds tile-commands */ + if (tile >= MapSize()) { + _cmd_text = NULL; + return false; + } + + assert(_docommand_recursive == 0); + + _error_message = INVALID_STRING_ID; + error_part1 = GB(cmd, 16, 16); + _additional_cash_required = 0; + + /** Spectator has no rights except for the (dedicated) server which + * is/can be a spectator but as the server it can do anything */ + if (_current_player == PLAYER_SPECTATOR && !_network_server) { + ShowErrorMessage(_error_message, error_part1, x, y); + _cmd_text = NULL; + return false; + } + + flags = 0; + if (cmd & CMD_AUTO) flags |= DC_AUTO; + if (cmd & CMD_NO_WATER) flags |= DC_NO_WATER; + + // get pointer to command handler + assert((cmd & 0xFF) < lengthof(_command_proc_table)); + proc = _command_proc_table[cmd & 0xFF].proc; + if (proc == NULL) { + _cmd_text = NULL; + return false; + } + + // Some commands have a different output in dryrun than the realrun + // e.g.: if you demolish a whole town, the dryrun would say okay. + // but by really destroying, your rating drops and at a certain point + // it will fail. so res and res2 are different + // CMD_REMOVE_ROAD: This command has special local authority + // restrictions which may cause the test run to fail (the previous + // road fragments still stay there and the town won't let you + // disconnect the road system), but the exec will succeed and this + // fact will trigger an assertion failure. --pasky + notest = + (cmd & 0xFF) == CMD_CLEAR_AREA || + (cmd & 0xFF) == CMD_CONVERT_RAIL || + (cmd & 0xFF) == CMD_LEVEL_LAND || + (cmd & 0xFF) == CMD_REMOVE_ROAD || + (cmd & 0xFF) == CMD_REMOVE_LONG_ROAD; + + _docommand_recursive = 1; + + // cost estimation only? + if (!IsGeneratingWorld() && + _shift_pressed && + IsLocalPlayer() && + !(cmd & (CMD_NETWORK_COMMAND | CMD_SHOW_NO_ERROR)) && + (cmd & 0xFF) != CMD_PAUSE) { + // estimate the cost. + res = proc(tile, flags, p1, p2); + if (CmdFailed(res)) { + if (res & 0xFFFF) _error_message = res & 0xFFFF; + ShowErrorMessage(_error_message, error_part1, x, y); + } else { + ShowEstimatedCostOrIncome(res, x, y); + } + + _docommand_recursive = 0; + _cmd_text = NULL; + return false; + } + + + if (!((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { + // first test if the command can be executed. + res = proc(tile, flags, p1, p2); + if (CmdFailed(res)) { + if (res & 0xFFFF) _error_message = res & 0xFFFF; + goto show_error; + } + // no money? Only check if notest is off + if (!notest && res != 0 && !CheckPlayerHasMoney(res)) goto show_error; + } + +#ifdef ENABLE_NETWORK + /** If we are in network, and the command is not from the network + * send it to the command-queue and abort execution + * If we are a dedicated server temporarily switch local player, otherwise + * the other parties won't be able to execute our command and will desync. + * We also need to do this if the server's company has gone bankrupt + * @todo Rewrite (dedicated) server to something more than a dirty hack! + */ + if (_networking && !(cmd & CMD_NETWORK_COMMAND)) { + PlayerID pbck = _local_player; + if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = 0; + NetworkSend_Command(tile, p1, p2, cmd, callback); + if (_network_dedicated || (_network_server && pbck == PLAYER_SPECTATOR)) _local_player = pbck; + _docommand_recursive = 0; + _cmd_text = NULL; + return true; + } +#endif /* ENABLE_NETWORK */ + + // update last build coordinate of player. + if (tile != 0 && IsValidPlayer(_current_player)) { + GetPlayer(_current_player)->last_build_coordinate = tile; + } + + /* Actually try and execute the command. If no cost-type is given + * use the construction one */ + _yearly_expenses_type = EXPENSES_CONSTRUCTION; + res2 = proc(tile, flags | DC_EXEC, p1, p2); + + // If notest is on, it means the result of the test can be different than + // the real command.. so ignore the test + if (!notest && !((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { + assert(res == res2); // sanity check + } else { + if (CmdFailed(res2)) { + if (res2 & 0xFFFF) _error_message = res2 & 0xFFFF; + goto show_error; + } + } + + SubtractMoneyFromPlayer(res2); + + if (IsLocalPlayer() && _game_mode != GM_EDITOR) { + if (res2 != 0) ShowCostOrIncomeAnimation(x, y, GetSlopeZ(x, y), res2); + if (_additional_cash_required) { + SetDParam(0, _additional_cash_required); + ShowErrorMessage(STR_0003_NOT_ENOUGH_CASH_REQUIRES, error_part1, x,y); + if (res2 == 0) goto callb_err; + } + } + + _docommand_recursive = 0; + + if (callback) callback(true, tile, p1, p2); + _cmd_text = NULL; + return true; + +show_error: + // show error message if the command fails? + if (IsLocalPlayer() && error_part1 != 0) { + ShowErrorMessage(_error_message, error_part1, x,y); + } + +callb_err: + _docommand_recursive = 0; + + if (callback) callback(false, tile, p1, p2); + _cmd_text = NULL; + return false; +} diff --git a/src/console.c b/src/console.c deleted file mode 100644 --- a/src/console.c +++ /dev/null @@ -1,1150 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "table/strings.h" -#include "functions.h" -#include "window.h" -#include "gui.h" -#include "gfx.h" -#include "player.h" -#include "variables.h" -#include "string.h" -#include -#include -#include "console.h" -#include "network/network.h" -#include "network/network_data.h" -#include "network/network_server.h" - -#define ICON_BUFFER 79 -#define ICON_HISTORY_SIZE 20 -#define ICON_LINE_HEIGHT 12 -#define ICON_RIGHT_BORDERWIDTH 10 -#define ICON_BOTTOM_BORDERWIDTH 12 -#define ICON_MAX_ALIAS_LINES 40 -#define ICON_TOKEN_COUNT 20 - -// ** main console ** // -static char *_iconsole_buffer[ICON_BUFFER + 1]; -static uint16 _iconsole_cbuffer[ICON_BUFFER + 1]; -static Textbuf _iconsole_cmdline; - -// ** stdlib ** // -byte _stdlib_developer = 1; -bool _stdlib_con_developer = false; -FILE *_iconsole_output_file; - -// ** main console cmd buffer -static char *_iconsole_history[ICON_HISTORY_SIZE]; -static byte _iconsole_historypos; - -/* *************** */ -/* end of header */ -/* *************** */ - -static void IConsoleClearCommand(void) -{ - memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE); - _iconsole_cmdline.length = 0; - _iconsole_cmdline.width = 0; - _iconsole_cmdline.caretpos = 0; - _iconsole_cmdline.caretxoffs = 0; - SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); -} - -static inline void IConsoleResetHistoryPos(void) {_iconsole_historypos = ICON_HISTORY_SIZE - 1;} - - -static void IConsoleHistoryAdd(const char *cmd); -static void IConsoleHistoryNavigate(int direction); - -// ** console window ** // -static void IConsoleWndProc(Window *w, WindowEvent *e) -{ - static byte iconsole_scroll = ICON_BUFFER; - - switch (e->event) { - case WE_PAINT: { - int i = iconsole_scroll; - int max = (w->height / ICON_LINE_HEIGHT) - 1; - int delta = 0; - GfxFillRect(w->left, w->top, w->width, w->height - 1, 0); - while ((i > 0) && (i > iconsole_scroll - max) && (_iconsole_buffer[i] != NULL)) { - DoDrawString(_iconsole_buffer[i], 5, - w->height - (iconsole_scroll + 2 - i) * ICON_LINE_HEIGHT, _iconsole_cbuffer[i]); - i--; - } - /* If the text is longer than the window, don't show the starting ']' */ - delta = w->width - 10 - _iconsole_cmdline.width - ICON_RIGHT_BORDERWIDTH; - if (delta > 0) { - DoDrawString("]", 5, w->height - ICON_LINE_HEIGHT, _icolour_cmd); - delta = 0; - } - - DoDrawString(_iconsole_cmdline.buf, 10 + delta, w->height - ICON_LINE_HEIGHT, _icolour_cmd); - - if (_iconsole_cmdline.caret) - DoDrawString("_", 10 + delta + _iconsole_cmdline.caretxoffs, w->height - ICON_LINE_HEIGHT, 12); - break; - } - case WE_MOUSELOOP: - if (HandleCaret(&_iconsole_cmdline)) - SetWindowDirty(w); - break; - case WE_DESTROY: - _iconsole_mode = ICONSOLE_CLOSED; - break; - case WE_KEYPRESS: - e->we.keypress.cont = false; - switch (e->we.keypress.keycode) { - case WKC_UP: - IConsoleHistoryNavigate(+1); - SetWindowDirty(w); - break; - case WKC_DOWN: - IConsoleHistoryNavigate(-1); - SetWindowDirty(w); - break; - case WKC_SHIFT | WKC_PAGEUP: - if (iconsole_scroll - (w->height / ICON_LINE_HEIGHT) - 1 < 0) { - iconsole_scroll = 0; - } else { - iconsole_scroll -= (w->height / ICON_LINE_HEIGHT) - 1; - } - SetWindowDirty(w); - break; - case WKC_SHIFT | WKC_PAGEDOWN: - if (iconsole_scroll + (w->height / ICON_LINE_HEIGHT) - 1 > ICON_BUFFER) { - iconsole_scroll = ICON_BUFFER; - } else { - iconsole_scroll += (w->height / ICON_LINE_HEIGHT) - 1; - } - SetWindowDirty(w); - break; - case WKC_SHIFT | WKC_UP: - if (iconsole_scroll <= 0) { - iconsole_scroll = 0; - } else { - --iconsole_scroll; - } - SetWindowDirty(w); - break; - case WKC_SHIFT | WKC_DOWN: - if (iconsole_scroll >= ICON_BUFFER) { - iconsole_scroll = ICON_BUFFER; - } else { - ++iconsole_scroll; - } - SetWindowDirty(w); - break; - case WKC_BACKQUOTE: - IConsoleSwitch(); - break; - case WKC_RETURN: case WKC_NUM_ENTER: - IConsolePrintF(_icolour_cmd, "] %s", _iconsole_cmdline.buf); - IConsoleHistoryAdd(_iconsole_cmdline.buf); - - IConsoleCmdExec(_iconsole_cmdline.buf); - IConsoleClearCommand(); - break; - case WKC_CTRL | WKC_RETURN: - _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL; - IConsoleResize(w); - MarkWholeScreenDirty(); - break; - case (WKC_CTRL | 'V'): - if (InsertTextBufferClipboard(&_iconsole_cmdline)) { - IConsoleResetHistoryPos(); - SetWindowDirty(w); - } - break; - case (WKC_CTRL | 'L'): - IConsoleCmdExec("clear"); - break; - case (WKC_CTRL | 'U'): - DeleteTextBufferAll(&_iconsole_cmdline); - SetWindowDirty(w); - break; - case WKC_BACKSPACE: case WKC_DELETE: - if (DeleteTextBufferChar(&_iconsole_cmdline, e->we.keypress.keycode)) { - IConsoleResetHistoryPos(); - SetWindowDirty(w); - } - break; - case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME: - if (MoveTextBufferPos(&_iconsole_cmdline, e->we.keypress.keycode)) { - IConsoleResetHistoryPos(); - SetWindowDirty(w); - } - break; - default: - if (IsValidChar(e->we.keypress.key, CS_ALPHANUMERAL)) { - iconsole_scroll = ICON_BUFFER; - InsertTextBufferChar(&_iconsole_cmdline, e->we.keypress.key); - IConsoleResetHistoryPos(); - SetWindowDirty(w); - } else { - e->we.keypress.cont = true; - } - break; - } - } -} - -static const Widget _iconsole_window_widgets[] = { - {WIDGETS_END} -}; - -static const WindowDesc _iconsole_window_desc = { - 0, 0, 2, 2, - WC_CONSOLE, 0, - WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, - _iconsole_window_widgets, - IConsoleWndProc, -}; - -void IConsoleInit(void) -{ - extern const char _openttd_revision[]; - _iconsole_output_file = NULL; - _icolour_def = 1; - _icolour_err = 3; - _icolour_warn = 13; - _icolour_dbg = 5; - _icolour_cmd = 2; - _iconsole_historypos = ICON_HISTORY_SIZE - 1; - _iconsole_mode = ICONSOLE_CLOSED; - -#ifdef ENABLE_NETWORK /* Initialize network only variables */ - _redirect_console_to_client = 0; -#endif - - memset(_iconsole_history, 0, sizeof(_iconsole_history)); - memset(_iconsole_buffer, 0, sizeof(_iconsole_buffer)); - memset(_iconsole_cbuffer, 0, sizeof(_iconsole_cbuffer)); - _iconsole_cmdline.buf = calloc(ICON_CMDLN_SIZE, sizeof(*_iconsole_cmdline.buf)); // create buffer and zero it - _iconsole_cmdline.maxlength = ICON_CMDLN_SIZE; - - IConsolePrintF(13, "OpenTTD Game Console Revision 7 - %s", _openttd_revision); - IConsolePrint(12, "------------------------------------"); - IConsolePrint(12, "use \"help\" for more information"); - IConsolePrint(12, ""); - IConsoleStdLibRegister(); - IConsoleClearCommand(); - IConsoleHistoryAdd(""); -} - -void IConsoleClearBuffer(void) -{ - uint i; - for (i = 0; i <= ICON_BUFFER; i++) { - free(_iconsole_buffer[i]); - _iconsole_buffer[i] = NULL; - } -} - -static void IConsoleClear(void) -{ - free(_iconsole_cmdline.buf); - IConsoleClearBuffer(); -} - -static void IConsoleWriteToLogFile(const char *string) -{ - if (_iconsole_output_file != NULL) { - // if there is an console output file ... also print it there - fwrite(string, strlen(string), 1, _iconsole_output_file); - fwrite("\n", 1, 1, _iconsole_output_file); - } -} - -bool CloseConsoleLogIfActive(void) -{ - if (_iconsole_output_file != NULL) { - IConsolePrintF(_icolour_def, "file output complete"); - fclose(_iconsole_output_file); - _iconsole_output_file = NULL; - return true; - } - - return false; -} - -void IConsoleFree(void) -{ - IConsoleClear(); - CloseConsoleLogIfActive(); -} - -void IConsoleResize(Window *w) -{ - switch (_iconsole_mode) { - case ICONSOLE_OPENED: - w->height = _screen.height / 3; - w->width = _screen.width; - break; - case ICONSOLE_FULL: - w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH; - w->width = _screen.width; - break; - default: return; - } - - MarkWholeScreenDirty(); -} - -void IConsoleSwitch(void) -{ - switch (_iconsole_mode) { - case ICONSOLE_CLOSED: { - Window *w = AllocateWindowDesc(&_iconsole_window_desc); - w->height = _screen.height / 3; - w->width = _screen.width; - _iconsole_mode = ICONSOLE_OPENED; - SETBIT(_no_scroll, SCROLL_CON); // override cursor arrows; the gamefield will not scroll - } break; - case ICONSOLE_OPENED: case ICONSOLE_FULL: - DeleteWindowById(WC_CONSOLE, 0); - _iconsole_mode = ICONSOLE_CLOSED; - CLRBIT(_no_scroll, SCROLL_CON); - break; - } - - MarkWholeScreenDirty(); -} - -void IConsoleClose(void) {if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();} -void IConsoleOpen(void) {if (_iconsole_mode == ICONSOLE_CLOSED) IConsoleSwitch();} - -/** - * Add the entered line into the history so you can look it back - * scroll, etc. Put it to the beginning as it is the latest text - * @param cmd Text to be entered into the 'history' - */ -static void IConsoleHistoryAdd(const char *cmd) -{ - free(_iconsole_history[ICON_HISTORY_SIZE - 1]); - - memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1)); - _iconsole_history[0] = strdup(cmd); - IConsoleResetHistoryPos(); -} - -/** - * Navigate Up/Down in the history of typed commands - * @param direction Go further back in history (+1), go to recently typed commands (-1) - */ -static void IConsoleHistoryNavigate(int direction) -{ - int i = _iconsole_historypos + direction; - - // watch out for overflows, just wrap around - if (i < 0) i = ICON_HISTORY_SIZE - 1; - if (i >= ICON_HISTORY_SIZE) i = 0; - - if (direction > 0) - if (_iconsole_history[i] == NULL) i = 0; - - if (direction < 0) { - while (i > 0 && _iconsole_history[i] == NULL) i--; - } - - _iconsole_historypos = i; - IConsoleClearCommand(); - // copy history to 'command prompt / bash' - assert(_iconsole_history[i] != NULL && IS_INT_INSIDE(i, 0, ICON_HISTORY_SIZE)); - ttd_strlcpy(_iconsole_cmdline.buf, _iconsole_history[i], _iconsole_cmdline.maxlength); - UpdateTextBufferSize(&_iconsole_cmdline); -} - -/** - * Handle the printing of text entered into the console or redirected there - * by any other means. Text can be redirected to other players in a network game - * as well as to a logfile. If the network server is a dedicated server, all activities - * are also logged. All lines to print are added to a temporary buffer which can be - * used as a history to print them onscreen - * @param color_code the colour of the command. Red in case of errors, etc. - * @param string the message entered or output on the console (notice, error, etc.) - */ -void IConsolePrint(uint16 color_code, const char *string) -{ - char *str; -#ifdef ENABLE_NETWORK - if (_redirect_console_to_client != 0) { - /* Redirect the string to the client */ - SEND_COMMAND(PACKET_SERVER_RCON)(NetworkFindClientStateFromIndex(_redirect_console_to_client), color_code, string); - return; - } -#endif - - /* Create a copy of the string, strip if of colours and invalid - * characters and (when applicable) assign it to the console buffer */ - str = strdup(string); - str_strip_colours(str); - str_validate(str); - - if (_network_dedicated) { - printf("%s\n", str); - IConsoleWriteToLogFile(str); - free(str); // free duplicated string since it's not used anymore - return; - } - - /* move up all the strings in the buffer one place and do the same for colour - * to accomodate for the new command/message */ - free(_iconsole_buffer[0]); - memmove(&_iconsole_buffer[0], &_iconsole_buffer[1], sizeof(_iconsole_buffer[0]) * ICON_BUFFER); - _iconsole_buffer[ICON_BUFFER] = str; - - memmove(&_iconsole_cbuffer[0], &_iconsole_cbuffer[1], sizeof(_iconsole_cbuffer[0]) * ICON_BUFFER); - _iconsole_cbuffer[ICON_BUFFER] = color_code; - - IConsoleWriteToLogFile(_iconsole_buffer[ICON_BUFFER]); - - SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); -} - -/** - * Handle the printing of text entered into the console or redirected there - * by any other means. Uses printf() style format, for more information look - * at @IConsolePrint() - */ -void CDECL IConsolePrintF(uint16 color_code, const char *s, ...) -{ - va_list va; - char buf[ICON_MAX_STREAMSIZE]; - - va_start(va, s); - vsnprintf(buf, sizeof(buf), s, va); - va_end(va); - - IConsolePrint(color_code, buf); -} - -/** - * It is possible to print debugging information to the console, - * which is achieved by using this function. Can only be used by - * @debug() in debug.c. You need at least a level 2 (developer) for debugging - * messages to show up - * @param dbg debugging category - * @param string debugging message - */ -void IConsoleDebug(const char *dbg, const char *string) -{ - if (_stdlib_developer > 1) - IConsolePrintF(_icolour_dbg, "dbg: [%s] %s", dbg, string); -} - -/** - * It is possible to print warnings to the console. These are mostly - * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for - * debugging messages to show up - */ -void IConsoleWarning(const char *string) -{ - if (_stdlib_developer > 0) - IConsolePrintF(_icolour_warn, "WARNING: %s", string); -} - -/** - * It is possible to print error information to the console. This can include - * game errors, or errors in general you would want the user to notice - */ -void IConsoleError(const char *string) -{ - IConsolePrintF(_icolour_err, "ERROR: %s", string); -} - -/** - * Change a string into its number representation. Supports - * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false' - * @param *value the variable a successfull conversion will be put in - * @param *arg the string to be converted - * @return Return true on success or false on failure - */ -bool GetArgumentInteger(uint32 *value, const char *arg) -{ - char *endptr; - - if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) { - *value = 1; - return true; - } - if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) { - *value = 0; - return true; - } - - *value = strtoul(arg, &endptr, 0); - return arg != endptr; -} - -// * ************************* * // -// * hooking code * // -// * ************************* * // -/** - * General internal hooking code that is the same for both commands and variables - * @param hooks @IConsoleHooks structure that will be set according to - * @param type type access trigger - * @param proc function called when the hook criteria is met - */ -static void IConsoleHookAdd(IConsoleHooks *hooks, IConsoleHookTypes type, IConsoleHook *proc) -{ - if (hooks == NULL || proc == NULL) return; - - switch (type) { - case ICONSOLE_HOOK_ACCESS: - hooks->access = proc; - break; - case ICONSOLE_HOOK_PRE_ACTION: - hooks->pre = proc; - break; - case ICONSOLE_HOOK_POST_ACTION: - hooks->post = proc; - break; - default: NOT_REACHED(); - } -} - -/** - * Handle any special hook triggers. If the hook type is met check if - * there is a function associated with that and if so, execute it - * @param hooks @IConsoleHooks structure that will be checked - * @param type type of hook, trigger that needs to be activated - * @return true on a successfull execution of the hook command or if there - * is no hook/trigger present at all. False otherwise - */ -static bool IConsoleHookHandle(const IConsoleHooks *hooks, IConsoleHookTypes type) -{ - IConsoleHook *proc = NULL; - if (hooks == NULL) return false; - - switch (type) { - case ICONSOLE_HOOK_ACCESS: - proc = hooks->access; - break; - case ICONSOLE_HOOK_PRE_ACTION: - proc = hooks->pre; - break; - case ICONSOLE_HOOK_POST_ACTION: - proc = hooks->post; - break; - default: NOT_REACHED(); - } - - return (proc == NULL) ? true : proc(); -} - -/** - * Add a hook to a command that will be triggered at certain points - * @param name name of the command that the hook is added to - * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) - * @param proc function called when the hook criteria is met - */ -void IConsoleCmdHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) -{ - IConsoleCmd *cmd = IConsoleCmdGet(name); - if (cmd == NULL) return; - IConsoleHookAdd(&cmd->hook, type, proc); -} - -/** - * Add a hook to a variable that will be triggered at certain points - * @param name name of the variable that the hook is added to - * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) - * @param proc function called when the hook criteria is met - */ -void IConsoleVarHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) -{ - IConsoleVar *var = IConsoleVarGet(name); - if (var == NULL) return; - IConsoleHookAdd(&var->hook, type, proc); -} - -/** - * Perhaps ugly macro, but this saves us the trouble of writing the same function - * three types, just with different variables. Yes, templates would be handy. It was - * either this define or an even more ugly void* magic function - */ -#define IConsoleAddSorted(_base, item_new, IConsoleType, type) \ -{ \ - IConsoleType *item, *item_before; \ - /* first command */ \ - if (_base == NULL) { \ - _base = item_new; \ - return; \ - } \ - \ - item_before = NULL; \ - item = _base; \ - \ - /* BEGIN - Alphabetically insert the commands into the linked list */ \ - while (item != NULL) { \ - int i = strcmp(item->name, item_new->name); \ - if (i == 0) { \ - IConsoleError(type " with this name already exists; insertion aborted"); \ - free(item_new); \ - return; \ - } \ - \ - if (i > 0) break; /* insert at this position */ \ - \ - item_before = item; \ - item = item->next; \ - } \ - \ - if (item_before == NULL) { \ - _base = item_new; \ - } else { \ - item_before->next = item_new; \ - } \ - \ - item_new->next = item; \ - /* END - Alphabetical insert */ \ -} - -/** - * Register a new command to be used in the console - * @param name name of the command that will be used - * @param proc function that will be called upon execution of command - */ -void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc) -{ - char *new_cmd = strdup(name); - IConsoleCmd *item_new = malloc(sizeof(IConsoleCmd)); - - item_new->next = NULL; - item_new->proc = proc; - item_new->name = new_cmd; - - item_new->hook.access = NULL; - item_new->hook.pre = NULL; - item_new->hook.post = NULL; - - IConsoleAddSorted(_iconsole_cmds, item_new, IConsoleCmd, "a command"); -} - -/** - * Find the command pointed to by its string - * @param name command to be found - * @return return Cmdstruct of the found command, or NULL on failure - */ -IConsoleCmd *IConsoleCmdGet(const char *name) -{ - IConsoleCmd *item; - - for (item = _iconsole_cmds; item != NULL; item = item->next) { - if (strcmp(item->name, name) == 0) return item; - } - return NULL; -} - -/** - * Register a an alias for an already existing command in the console - * @param name name of the alias that will be used - * @param cmd name of the command that 'name' will be alias of - */ -void IConsoleAliasRegister(const char *name, const char *cmd) -{ - char *new_alias = strdup(name); - char *cmd_aliased = strdup(cmd); - IConsoleAlias *item_new = malloc(sizeof(IConsoleAlias)); - - item_new->next = NULL; - item_new->cmdline = cmd_aliased; - item_new->name = new_alias; - - IConsoleAddSorted(_iconsole_aliases, item_new, IConsoleAlias, "an alias"); -} - -/** - * Find the alias pointed to by its string - * @param name alias to be found - * @return return Aliasstruct of the found alias, or NULL on failure - */ -IConsoleAlias *IConsoleAliasGet(const char *name) -{ - IConsoleAlias* item; - - for (item = _iconsole_aliases; item != NULL; item = item->next) { - if (strcmp(item->name, name) == 0) return item; - } - - return NULL; -} - -/** copy in an argument into the aliasstream */ -static inline int IConsoleCopyInParams(char *dst, const char *src, uint bufpos) -{ - int len = min(ICON_MAX_STREAMSIZE - bufpos, (uint)strlen(src)); - strncpy(dst, src, len); - - return len; -} - -/** - * An alias is just another name for a command, or for more commands - * Execute it as well. - * @param *alias is the alias of the command - * @param tokencount the number of parameters passed - * @param *tokens are the parameters given to the original command (0 is the first param) - */ -static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT]) -{ - const char *cmdptr; - char *aliases[ICON_MAX_ALIAS_LINES], aliasstream[ICON_MAX_STREAMSIZE]; - uint i; - uint a_index, astream_i; - - memset(&aliases, 0, sizeof(aliases)); - memset(&aliasstream, 0, sizeof(aliasstream)); - - if (_stdlib_con_developer) - IConsolePrintF(_icolour_dbg, "condbg: requested command is an alias; parsing..."); - - aliases[0] = aliasstream; - for (cmdptr = alias->cmdline, a_index = 0, astream_i = 0; *cmdptr != '\0'; cmdptr++) { - if (a_index >= lengthof(aliases) || astream_i >= lengthof(aliasstream)) break; - - switch (*cmdptr) { - case '\'': /* ' will double for "" */ - aliasstream[astream_i++] = '"'; - break; - case ';': /* Cmd seperator, start new command */ - aliasstream[astream_i] = '\0'; - aliases[++a_index] = &aliasstream[++astream_i]; - cmdptr++; - break; - case '%': /* Some or all parameters */ - cmdptr++; - switch (*cmdptr) { - case '+': { /* All parameters seperated: "[param 1]" "[param 2]" */ - for (i = 0; i != tokencount; i++) { - aliasstream[astream_i++] = '"'; - astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); - aliasstream[astream_i++] = '"'; - aliasstream[astream_i++] = ' '; - } - } break; - case '!': { /* Merge the parameters to one: "[param 1] [param 2] [param 3...]" */ - aliasstream[astream_i++] = '"'; - for (i = 0; i != tokencount; i++) { - astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); - aliasstream[astream_i++] = ' '; - } - aliasstream[astream_i++] = '"'; - - } break; - default: { /* One specific parameter: %A = [param 1] %B = [param 2] ... */ - int param = *cmdptr - 'A'; - - if (param < 0 || param >= tokencount) { - IConsoleError("too many or wrong amount of parameters passed to alias, aborting"); - IConsolePrintF(_icolour_warn, "Usage of alias '%s': %s", alias->name, alias->cmdline); - return; - } - - aliasstream[astream_i++] = '"'; - astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[param], astream_i); - aliasstream[astream_i++] = '"'; - } break; - } break; - - default: - aliasstream[astream_i++] = *cmdptr; - break; - } - } - - for (i = 0; i <= a_index; i++) IConsoleCmdExec(aliases[i]); // execute each alias in turn -} - -/** - * Special function for adding string-type variables. They in addition - * also need a 'size' value saying how long their string buffer is. - * @param size the length of the string buffer - * For more information see @IConsoleVarRegister() - */ -void IConsoleVarStringRegister(const char *name, void *addr, uint32 size, const char *help) -{ - IConsoleVar *var; - IConsoleVarRegister(name, addr, ICONSOLE_VAR_STRING, help); - var = IConsoleVarGet(name); - var->size = size; -} - -/** - * Register a new variable to be used in the console - * @param name name of the variable that will be used - * @param addr memory location the variable will point to - * @param help the help string shown for the variable - * @param type the type of the variable (simple atomic) so we know which values it can get - */ -void IConsoleVarRegister(const char *name, void *addr, IConsoleVarTypes type, const char *help) -{ - char *new_cmd = strdup(name); - IConsoleVar *item_new = malloc(sizeof(IConsoleVar)); - - item_new->help = (help != NULL) ? strdup(help) : NULL; - - item_new->next = NULL; - item_new->name = new_cmd; - item_new->addr = addr; - item_new->proc = NULL; - item_new->type = type; - - item_new->hook.access = NULL; - item_new->hook.pre = NULL; - item_new->hook.post = NULL; - - IConsoleAddSorted(_iconsole_vars, item_new, IConsoleVar, "a variable"); -} - -/** - * Find the variable pointed to by its string - * @param name variable to be found - * @return return Varstruct of the found variable, or NULL on failure - */ -IConsoleVar *IConsoleVarGet(const char *name) -{ - IConsoleVar *item; - for (item = _iconsole_vars; item != NULL; item = item->next) { - if (strcmp(item->name, name) == 0) return item; - } - - return NULL; -} - -/** - * Set a new value to a console variable - * @param *var the variable being set/changed - * @param value the new value given to the variable, cast properly - */ -static void IConsoleVarSetValue(const IConsoleVar *var, uint32 value) -{ - IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); - switch (var->type) { - case ICONSOLE_VAR_BOOLEAN: - *(bool*)var->addr = (value != 0); - break; - case ICONSOLE_VAR_BYTE: - *(byte*)var->addr = (byte)value; - break; - case ICONSOLE_VAR_UINT16: - *(uint16*)var->addr = (uint16)value; - break; - case ICONSOLE_VAR_INT16: - *(int16*)var->addr = (int16)value; - break; - case ICONSOLE_VAR_UINT32: - *(uint32*)var->addr = (uint32)value; - break; - case ICONSOLE_VAR_INT32: - *(int32*)var->addr = (int32)value; - break; - default: NOT_REACHED(); - } - - IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); - IConsoleVarPrintSetValue(var); -} - -/** - * Set a new value to a string-type variable. Basically this - * means to copy the new value over to the container. - * @param *var the variable in question - * @param *value the new value - */ -static void IConsoleVarSetStringvalue(const IConsoleVar *var, const char *value) -{ - if (var->type != ICONSOLE_VAR_STRING || var->addr == NULL) return; - - IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); - ttd_strlcpy(var->addr, value, var->size); - IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); - IConsoleVarPrintSetValue(var); // print out the new value, giving feedback - return; -} - -/** - * Query the current value of a variable and return it - * @param *var the variable queried - * @return current value of the variable - */ -static uint32 IConsoleVarGetValue(const IConsoleVar *var) -{ - uint32 result = 0; - - switch (var->type) { - case ICONSOLE_VAR_BOOLEAN: - result = *(bool*)var->addr; - break; - case ICONSOLE_VAR_BYTE: - result = *(byte*)var->addr; - break; - case ICONSOLE_VAR_UINT16: - result = *(uint16*)var->addr; - break; - case ICONSOLE_VAR_INT16: - result = *(int16*)var->addr; - break; - case ICONSOLE_VAR_UINT32: - result = *(uint32*)var->addr; - break; - case ICONSOLE_VAR_INT32: - result = *(int32*)var->addr; - break; - default: NOT_REACHED(); - } - return result; -} - -/** - * Get the value of the variable and put it into a printable - * string form so we can use it for printing - */ -static char *IConsoleVarGetStringValue(const IConsoleVar *var) -{ - static char tempres[50]; - char *value = tempres; - - switch (var->type) { - case ICONSOLE_VAR_BOOLEAN: - snprintf(tempres, sizeof(tempres), "%s", (*(bool*)var->addr) ? "on" : "off"); - break; - case ICONSOLE_VAR_BYTE: - snprintf(tempres, sizeof(tempres), "%u", *(byte*)var->addr); - break; - case ICONSOLE_VAR_UINT16: - snprintf(tempres, sizeof(tempres), "%u", *(uint16*)var->addr); - break; - case ICONSOLE_VAR_UINT32: - snprintf(tempres, sizeof(tempres), "%u", *(uint32*)var->addr); - break; - case ICONSOLE_VAR_INT16: - snprintf(tempres, sizeof(tempres), "%i", *(int16*)var->addr); - break; - case ICONSOLE_VAR_INT32: - snprintf(tempres, sizeof(tempres), "%i", *(int32*)var->addr); - break; - case ICONSOLE_VAR_STRING: - value = (char*)var->addr; - break; - default: NOT_REACHED(); - } - - return value; -} - -/** - * Print out the value of the variable when asked - */ -void IConsoleVarPrintGetValue(const IConsoleVar *var) -{ - char *value; - /* Some variables need really specific handling, handle this in its - * callback function */ - if (var->proc != NULL) { - var->proc(0, NULL); - return; - } - - value = IConsoleVarGetStringValue(var); - IConsolePrintF(_icolour_warn, "Current value for '%s' is: %s", var->name, value); -} - -/** - * Print out the value of the variable after it has been assigned - * a new value, thus giving us feedback on the action - */ -void IConsoleVarPrintSetValue(const IConsoleVar *var) -{ - char *value = IConsoleVarGetStringValue(var); - IConsolePrintF(_icolour_warn, "'%s' changed to: %s", var->name, value); -} - -/** - * Execute a variable command. Without any parameters, print out its value - * with parameters it assigns a new value to the variable - * @param *var the variable that we will be querying/changing - * @param tokencount how many additional parameters have been given to the commandline - * @param *token the actual parameters the variable was called with - */ -void IConsoleVarExec(const IConsoleVar *var, byte tokencount, char *token[ICON_TOKEN_COUNT]) -{ - const char *tokenptr = token[0]; - byte t_index = tokencount; - uint32 value; - - if (_stdlib_con_developer) - IConsolePrintF(_icolour_dbg, "condbg: requested command is a variable"); - - if (tokencount == 0) { /* Just print out value */ - IConsoleVarPrintGetValue(var); - return; - } - - /* Use of assignment sign is not mandatory but supported, so just 'ignore it appropiately' */ - if (strcmp(tokenptr, "=") == 0) tokencount--; - - if (tokencount == 1) { - /* Some variables need really special handling, handle it in their callback procedure */ - if (var->proc != NULL) { - var->proc(tokencount, &token[t_index - tokencount]); // set the new value - return; - } - /* Strings need special processing. No need to convert the argument to - * an integer value, just copy over the argument on a one-by-one basis */ - if (var->type == ICONSOLE_VAR_STRING) { - IConsoleVarSetStringvalue(var, token[t_index - tokencount]); - return; - } else if (GetArgumentInteger(&value, token[t_index - tokencount])) { - IConsoleVarSetValue(var, value); - return; - } - - /* Increase or decrease the value by one. This of course can only happen to 'number' types */ - if (strcmp(tokenptr, "++") == 0 && var->type != ICONSOLE_VAR_STRING) { - IConsoleVarSetValue(var, IConsoleVarGetValue(var) + 1); - return; - } - - if (strcmp(tokenptr, "--") == 0 && var->type != ICONSOLE_VAR_STRING) { - IConsoleVarSetValue(var, IConsoleVarGetValue(var) - 1); - return; - } - } - - IConsoleError("invalid variable assignment"); -} - -/** - * Add a callback function to the variable. Some variables need - * very special processing, which can only be done with custom code - * @param name name of the variable the callback function is added to - * @param proc the function called - */ -void IConsoleVarProcAdd(const char *name, IConsoleCmdProc *proc) -{ - IConsoleVar *var = IConsoleVarGet(name); - if (var == NULL) return; - var->proc = proc; -} - -/** - * Execute a given command passed to us. First chop it up into - * individual tokens (seperated by spaces), then execute it if possible - * @param cmdstr string to be parsed and executed - */ -void IConsoleCmdExec(const char *cmdstr) -{ - IConsoleCmd *cmd = NULL; - IConsoleAlias *alias = NULL; - IConsoleVar *var = NULL; - - const char *cmdptr; - char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE]; - uint t_index, tstream_i; - - bool longtoken = false; - bool foundtoken = false; - - if (cmdstr[0] == '#') return; // comments - - for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) { - if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) { - IConsoleError("command contains malformed characters, aborting"); - IConsolePrintF(_icolour_err, "ERROR: command was: '%s'", cmdstr); - return; - } - } - - if (_stdlib_con_developer) - IConsolePrintF(_icolour_dbg, "condbg: executing cmdline: '%s'", cmdstr); - - memset(&tokens, 0, sizeof(tokens)); - memset(&tokenstream, 0, sizeof(tokenstream)); - - /* 1. Split up commandline into tokens, seperated by spaces, commands - * enclosed in "" are taken as one token. We can only go as far as the amount - * of characters in our stream or the max amount of tokens we can handle */ - for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) { - if (t_index >= lengthof(tokens) || tstream_i >= lengthof(tokenstream)) break; - - switch (*cmdptr) { - case ' ': /* Token seperator */ - if (!foundtoken) break; - - if (longtoken) { - tokenstream[tstream_i] = *cmdptr; - } else { - tokenstream[tstream_i] = '\0'; - foundtoken = false; - } - - tstream_i++; - break; - case '"': /* Tokens enclosed in "" are one token */ - longtoken = !longtoken; - break; - case '\\': /* Escape character for "" */ - if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) { - tokenstream[tstream_i++] = *++cmdptr; - break; - } - /* fallthrough */ - default: /* Normal character */ - tokenstream[tstream_i++] = *cmdptr; - - if (!foundtoken) { - tokens[t_index++] = &tokenstream[tstream_i - 1]; - foundtoken = true; - } - break; - } - } - - if (_stdlib_con_developer) { - uint i; - - for (i = 0; tokens[i] != NULL; i++) { - IConsolePrintF(_icolour_dbg, "condbg: token %d is: '%s'", i, tokens[i]); - } - } - - if (tokens[0] == '\0') return; // don't execute empty commands - /* 2. Determine type of command (cmd, alias or variable) and execute - * First try commands, then aliases, and finally variables. Execute - * the found action taking into account its hooking code - */ - cmd = IConsoleCmdGet(tokens[0]); - if (cmd != NULL) { - if (IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_ACCESS)) { - IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_PRE_ACTION); - if (cmd->proc(t_index, tokens)) { // index started with 0 - IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_POST_ACTION); - } else { - cmd->proc(0, NULL); // if command failed, give help - } - } - return; - } - - t_index--; // ignore the variable-name for comfort for both aliases and variaables - alias = IConsoleAliasGet(tokens[0]); - if (alias != NULL) { - IConsoleAliasExec(alias, t_index, &tokens[1]); - return; - } - - var = IConsoleVarGet(tokens[0]); - if (var != NULL) { - if (IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_ACCESS)) { - IConsoleVarExec(var, t_index, &tokens[1]); - } - return; - } - - IConsoleError("command or variable not found"); -} diff --git a/src/console.cpp b/src/console.cpp new file mode 100644 --- /dev/null +++ b/src/console.cpp @@ -0,0 +1,1150 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "table/strings.h" +#include "functions.h" +#include "window.h" +#include "gui.h" +#include "gfx.h" +#include "player.h" +#include "variables.h" +#include "string.h" +#include +#include +#include "console.h" +#include "network/network.h" +#include "network/network_data.h" +#include "network/network_server.h" + +#define ICON_BUFFER 79 +#define ICON_HISTORY_SIZE 20 +#define ICON_LINE_HEIGHT 12 +#define ICON_RIGHT_BORDERWIDTH 10 +#define ICON_BOTTOM_BORDERWIDTH 12 +#define ICON_MAX_ALIAS_LINES 40 +#define ICON_TOKEN_COUNT 20 + +// ** main console ** // +static char *_iconsole_buffer[ICON_BUFFER + 1]; +static uint16 _iconsole_cbuffer[ICON_BUFFER + 1]; +static Textbuf _iconsole_cmdline; + +// ** stdlib ** // +byte _stdlib_developer = 1; +bool _stdlib_con_developer = false; +FILE *_iconsole_output_file; + +// ** main console cmd buffer +static char *_iconsole_history[ICON_HISTORY_SIZE]; +static byte _iconsole_historypos; + +/* *************** */ +/* end of header */ +/* *************** */ + +static void IConsoleClearCommand(void) +{ + memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE); + _iconsole_cmdline.length = 0; + _iconsole_cmdline.width = 0; + _iconsole_cmdline.caretpos = 0; + _iconsole_cmdline.caretxoffs = 0; + SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); +} + +static inline void IConsoleResetHistoryPos(void) {_iconsole_historypos = ICON_HISTORY_SIZE - 1;} + + +static void IConsoleHistoryAdd(const char *cmd); +static void IConsoleHistoryNavigate(int direction); + +// ** console window ** // +static void IConsoleWndProc(Window *w, WindowEvent *e) +{ + static byte iconsole_scroll = ICON_BUFFER; + + switch (e->event) { + case WE_PAINT: { + int i = iconsole_scroll; + int max = (w->height / ICON_LINE_HEIGHT) - 1; + int delta = 0; + GfxFillRect(w->left, w->top, w->width, w->height - 1, 0); + while ((i > 0) && (i > iconsole_scroll - max) && (_iconsole_buffer[i] != NULL)) { + DoDrawString(_iconsole_buffer[i], 5, + w->height - (iconsole_scroll + 2 - i) * ICON_LINE_HEIGHT, _iconsole_cbuffer[i]); + i--; + } + /* If the text is longer than the window, don't show the starting ']' */ + delta = w->width - 10 - _iconsole_cmdline.width - ICON_RIGHT_BORDERWIDTH; + if (delta > 0) { + DoDrawString("]", 5, w->height - ICON_LINE_HEIGHT, _icolour_cmd); + delta = 0; + } + + DoDrawString(_iconsole_cmdline.buf, 10 + delta, w->height - ICON_LINE_HEIGHT, _icolour_cmd); + + if (_iconsole_cmdline.caret) + DoDrawString("_", 10 + delta + _iconsole_cmdline.caretxoffs, w->height - ICON_LINE_HEIGHT, 12); + break; + } + case WE_MOUSELOOP: + if (HandleCaret(&_iconsole_cmdline)) + SetWindowDirty(w); + break; + case WE_DESTROY: + _iconsole_mode = ICONSOLE_CLOSED; + break; + case WE_KEYPRESS: + e->we.keypress.cont = false; + switch (e->we.keypress.keycode) { + case WKC_UP: + IConsoleHistoryNavigate(+1); + SetWindowDirty(w); + break; + case WKC_DOWN: + IConsoleHistoryNavigate(-1); + SetWindowDirty(w); + break; + case WKC_SHIFT | WKC_PAGEUP: + if (iconsole_scroll - (w->height / ICON_LINE_HEIGHT) - 1 < 0) { + iconsole_scroll = 0; + } else { + iconsole_scroll -= (w->height / ICON_LINE_HEIGHT) - 1; + } + SetWindowDirty(w); + break; + case WKC_SHIFT | WKC_PAGEDOWN: + if (iconsole_scroll + (w->height / ICON_LINE_HEIGHT) - 1 > ICON_BUFFER) { + iconsole_scroll = ICON_BUFFER; + } else { + iconsole_scroll += (w->height / ICON_LINE_HEIGHT) - 1; + } + SetWindowDirty(w); + break; + case WKC_SHIFT | WKC_UP: + if (iconsole_scroll <= 0) { + iconsole_scroll = 0; + } else { + --iconsole_scroll; + } + SetWindowDirty(w); + break; + case WKC_SHIFT | WKC_DOWN: + if (iconsole_scroll >= ICON_BUFFER) { + iconsole_scroll = ICON_BUFFER; + } else { + ++iconsole_scroll; + } + SetWindowDirty(w); + break; + case WKC_BACKQUOTE: + IConsoleSwitch(); + break; + case WKC_RETURN: case WKC_NUM_ENTER: + IConsolePrintF(_icolour_cmd, "] %s", _iconsole_cmdline.buf); + IConsoleHistoryAdd(_iconsole_cmdline.buf); + + IConsoleCmdExec(_iconsole_cmdline.buf); + IConsoleClearCommand(); + break; + case WKC_CTRL | WKC_RETURN: + _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL; + IConsoleResize(w); + MarkWholeScreenDirty(); + break; + case (WKC_CTRL | 'V'): + if (InsertTextBufferClipboard(&_iconsole_cmdline)) { + IConsoleResetHistoryPos(); + SetWindowDirty(w); + } + break; + case (WKC_CTRL | 'L'): + IConsoleCmdExec("clear"); + break; + case (WKC_CTRL | 'U'): + DeleteTextBufferAll(&_iconsole_cmdline); + SetWindowDirty(w); + break; + case WKC_BACKSPACE: case WKC_DELETE: + if (DeleteTextBufferChar(&_iconsole_cmdline, e->we.keypress.keycode)) { + IConsoleResetHistoryPos(); + SetWindowDirty(w); + } + break; + case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME: + if (MoveTextBufferPos(&_iconsole_cmdline, e->we.keypress.keycode)) { + IConsoleResetHistoryPos(); + SetWindowDirty(w); + } + break; + default: + if (IsValidChar(e->we.keypress.key, CS_ALPHANUMERAL)) { + iconsole_scroll = ICON_BUFFER; + InsertTextBufferChar(&_iconsole_cmdline, e->we.keypress.key); + IConsoleResetHistoryPos(); + SetWindowDirty(w); + } else { + e->we.keypress.cont = true; + } + break; + } + } +} + +static const Widget _iconsole_window_widgets[] = { + {WIDGETS_END} +}; + +static const WindowDesc _iconsole_window_desc = { + 0, 0, 2, 2, + WC_CONSOLE, 0, + WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, + _iconsole_window_widgets, + IConsoleWndProc, +}; + +void IConsoleInit(void) +{ + extern const char _openttd_revision[]; + _iconsole_output_file = NULL; + _icolour_def = 1; + _icolour_err = 3; + _icolour_warn = 13; + _icolour_dbg = 5; + _icolour_cmd = 2; + _iconsole_historypos = ICON_HISTORY_SIZE - 1; + _iconsole_mode = ICONSOLE_CLOSED; + +#ifdef ENABLE_NETWORK /* Initialize network only variables */ + _redirect_console_to_client = 0; +#endif + + memset(_iconsole_history, 0, sizeof(_iconsole_history)); + memset(_iconsole_buffer, 0, sizeof(_iconsole_buffer)); + memset(_iconsole_cbuffer, 0, sizeof(_iconsole_cbuffer)); + _iconsole_cmdline.buf = calloc(ICON_CMDLN_SIZE, sizeof(*_iconsole_cmdline.buf)); // create buffer and zero it + _iconsole_cmdline.maxlength = ICON_CMDLN_SIZE; + + IConsolePrintF(13, "OpenTTD Game Console Revision 7 - %s", _openttd_revision); + IConsolePrint(12, "------------------------------------"); + IConsolePrint(12, "use \"help\" for more information"); + IConsolePrint(12, ""); + IConsoleStdLibRegister(); + IConsoleClearCommand(); + IConsoleHistoryAdd(""); +} + +void IConsoleClearBuffer(void) +{ + uint i; + for (i = 0; i <= ICON_BUFFER; i++) { + free(_iconsole_buffer[i]); + _iconsole_buffer[i] = NULL; + } +} + +static void IConsoleClear(void) +{ + free(_iconsole_cmdline.buf); + IConsoleClearBuffer(); +} + +static void IConsoleWriteToLogFile(const char *string) +{ + if (_iconsole_output_file != NULL) { + // if there is an console output file ... also print it there + fwrite(string, strlen(string), 1, _iconsole_output_file); + fwrite("\n", 1, 1, _iconsole_output_file); + } +} + +bool CloseConsoleLogIfActive(void) +{ + if (_iconsole_output_file != NULL) { + IConsolePrintF(_icolour_def, "file output complete"); + fclose(_iconsole_output_file); + _iconsole_output_file = NULL; + return true; + } + + return false; +} + +void IConsoleFree(void) +{ + IConsoleClear(); + CloseConsoleLogIfActive(); +} + +void IConsoleResize(Window *w) +{ + switch (_iconsole_mode) { + case ICONSOLE_OPENED: + w->height = _screen.height / 3; + w->width = _screen.width; + break; + case ICONSOLE_FULL: + w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH; + w->width = _screen.width; + break; + default: return; + } + + MarkWholeScreenDirty(); +} + +void IConsoleSwitch(void) +{ + switch (_iconsole_mode) { + case ICONSOLE_CLOSED: { + Window *w = AllocateWindowDesc(&_iconsole_window_desc); + w->height = _screen.height / 3; + w->width = _screen.width; + _iconsole_mode = ICONSOLE_OPENED; + SETBIT(_no_scroll, SCROLL_CON); // override cursor arrows; the gamefield will not scroll + } break; + case ICONSOLE_OPENED: case ICONSOLE_FULL: + DeleteWindowById(WC_CONSOLE, 0); + _iconsole_mode = ICONSOLE_CLOSED; + CLRBIT(_no_scroll, SCROLL_CON); + break; + } + + MarkWholeScreenDirty(); +} + +void IConsoleClose(void) {if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();} +void IConsoleOpen(void) {if (_iconsole_mode == ICONSOLE_CLOSED) IConsoleSwitch();} + +/** + * Add the entered line into the history so you can look it back + * scroll, etc. Put it to the beginning as it is the latest text + * @param cmd Text to be entered into the 'history' + */ +static void IConsoleHistoryAdd(const char *cmd) +{ + free(_iconsole_history[ICON_HISTORY_SIZE - 1]); + + memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1)); + _iconsole_history[0] = strdup(cmd); + IConsoleResetHistoryPos(); +} + +/** + * Navigate Up/Down in the history of typed commands + * @param direction Go further back in history (+1), go to recently typed commands (-1) + */ +static void IConsoleHistoryNavigate(int direction) +{ + int i = _iconsole_historypos + direction; + + // watch out for overflows, just wrap around + if (i < 0) i = ICON_HISTORY_SIZE - 1; + if (i >= ICON_HISTORY_SIZE) i = 0; + + if (direction > 0) + if (_iconsole_history[i] == NULL) i = 0; + + if (direction < 0) { + while (i > 0 && _iconsole_history[i] == NULL) i--; + } + + _iconsole_historypos = i; + IConsoleClearCommand(); + // copy history to 'command prompt / bash' + assert(_iconsole_history[i] != NULL && IS_INT_INSIDE(i, 0, ICON_HISTORY_SIZE)); + ttd_strlcpy(_iconsole_cmdline.buf, _iconsole_history[i], _iconsole_cmdline.maxlength); + UpdateTextBufferSize(&_iconsole_cmdline); +} + +/** + * Handle the printing of text entered into the console or redirected there + * by any other means. Text can be redirected to other players in a network game + * as well as to a logfile. If the network server is a dedicated server, all activities + * are also logged. All lines to print are added to a temporary buffer which can be + * used as a history to print them onscreen + * @param color_code the colour of the command. Red in case of errors, etc. + * @param string the message entered or output on the console (notice, error, etc.) + */ +void IConsolePrint(uint16 color_code, const char *string) +{ + char *str; +#ifdef ENABLE_NETWORK + if (_redirect_console_to_client != 0) { + /* Redirect the string to the client */ + SEND_COMMAND(PACKET_SERVER_RCON)(NetworkFindClientStateFromIndex(_redirect_console_to_client), color_code, string); + return; + } +#endif + + /* Create a copy of the string, strip if of colours and invalid + * characters and (when applicable) assign it to the console buffer */ + str = strdup(string); + str_strip_colours(str); + str_validate(str); + + if (_network_dedicated) { + printf("%s\n", str); + IConsoleWriteToLogFile(str); + free(str); // free duplicated string since it's not used anymore + return; + } + + /* move up all the strings in the buffer one place and do the same for colour + * to accomodate for the new command/message */ + free(_iconsole_buffer[0]); + memmove(&_iconsole_buffer[0], &_iconsole_buffer[1], sizeof(_iconsole_buffer[0]) * ICON_BUFFER); + _iconsole_buffer[ICON_BUFFER] = str; + + memmove(&_iconsole_cbuffer[0], &_iconsole_cbuffer[1], sizeof(_iconsole_cbuffer[0]) * ICON_BUFFER); + _iconsole_cbuffer[ICON_BUFFER] = color_code; + + IConsoleWriteToLogFile(_iconsole_buffer[ICON_BUFFER]); + + SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); +} + +/** + * Handle the printing of text entered into the console or redirected there + * by any other means. Uses printf() style format, for more information look + * at @IConsolePrint() + */ +void CDECL IConsolePrintF(uint16 color_code, const char *s, ...) +{ + va_list va; + char buf[ICON_MAX_STREAMSIZE]; + + va_start(va, s); + vsnprintf(buf, sizeof(buf), s, va); + va_end(va); + + IConsolePrint(color_code, buf); +} + +/** + * It is possible to print debugging information to the console, + * which is achieved by using this function. Can only be used by + * @debug() in debug.c. You need at least a level 2 (developer) for debugging + * messages to show up + * @param dbg debugging category + * @param string debugging message + */ +void IConsoleDebug(const char *dbg, const char *string) +{ + if (_stdlib_developer > 1) + IConsolePrintF(_icolour_dbg, "dbg: [%s] %s", dbg, string); +} + +/** + * It is possible to print warnings to the console. These are mostly + * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for + * debugging messages to show up + */ +void IConsoleWarning(const char *string) +{ + if (_stdlib_developer > 0) + IConsolePrintF(_icolour_warn, "WARNING: %s", string); +} + +/** + * It is possible to print error information to the console. This can include + * game errors, or errors in general you would want the user to notice + */ +void IConsoleError(const char *string) +{ + IConsolePrintF(_icolour_err, "ERROR: %s", string); +} + +/** + * Change a string into its number representation. Supports + * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false' + * @param *value the variable a successfull conversion will be put in + * @param *arg the string to be converted + * @return Return true on success or false on failure + */ +bool GetArgumentInteger(uint32 *value, const char *arg) +{ + char *endptr; + + if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) { + *value = 1; + return true; + } + if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) { + *value = 0; + return true; + } + + *value = strtoul(arg, &endptr, 0); + return arg != endptr; +} + +// * ************************* * // +// * hooking code * // +// * ************************* * // +/** + * General internal hooking code that is the same for both commands and variables + * @param hooks @IConsoleHooks structure that will be set according to + * @param type type access trigger + * @param proc function called when the hook criteria is met + */ +static void IConsoleHookAdd(IConsoleHooks *hooks, IConsoleHookTypes type, IConsoleHook *proc) +{ + if (hooks == NULL || proc == NULL) return; + + switch (type) { + case ICONSOLE_HOOK_ACCESS: + hooks->access = proc; + break; + case ICONSOLE_HOOK_PRE_ACTION: + hooks->pre = proc; + break; + case ICONSOLE_HOOK_POST_ACTION: + hooks->post = proc; + break; + default: NOT_REACHED(); + } +} + +/** + * Handle any special hook triggers. If the hook type is met check if + * there is a function associated with that and if so, execute it + * @param hooks @IConsoleHooks structure that will be checked + * @param type type of hook, trigger that needs to be activated + * @return true on a successfull execution of the hook command or if there + * is no hook/trigger present at all. False otherwise + */ +static bool IConsoleHookHandle(const IConsoleHooks *hooks, IConsoleHookTypes type) +{ + IConsoleHook *proc = NULL; + if (hooks == NULL) return false; + + switch (type) { + case ICONSOLE_HOOK_ACCESS: + proc = hooks->access; + break; + case ICONSOLE_HOOK_PRE_ACTION: + proc = hooks->pre; + break; + case ICONSOLE_HOOK_POST_ACTION: + proc = hooks->post; + break; + default: NOT_REACHED(); + } + + return (proc == NULL) ? true : proc(); +} + +/** + * Add a hook to a command that will be triggered at certain points + * @param name name of the command that the hook is added to + * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) + * @param proc function called when the hook criteria is met + */ +void IConsoleCmdHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) +{ + IConsoleCmd *cmd = IConsoleCmdGet(name); + if (cmd == NULL) return; + IConsoleHookAdd(&cmd->hook, type, proc); +} + +/** + * Add a hook to a variable that will be triggered at certain points + * @param name name of the variable that the hook is added to + * @param type type of hook that is added (ACCESS, BEFORE and AFTER change) + * @param proc function called when the hook criteria is met + */ +void IConsoleVarHookAdd(const char *name, IConsoleHookTypes type, IConsoleHook *proc) +{ + IConsoleVar *var = IConsoleVarGet(name); + if (var == NULL) return; + IConsoleHookAdd(&var->hook, type, proc); +} + +/** + * Perhaps ugly macro, but this saves us the trouble of writing the same function + * three types, just with different variables. Yes, templates would be handy. It was + * either this define or an even more ugly void* magic function + */ +#define IConsoleAddSorted(_base, item_new, IConsoleType, type) \ +{ \ + IConsoleType *item, *item_before; \ + /* first command */ \ + if (_base == NULL) { \ + _base = item_new; \ + return; \ + } \ + \ + item_before = NULL; \ + item = _base; \ + \ + /* BEGIN - Alphabetically insert the commands into the linked list */ \ + while (item != NULL) { \ + int i = strcmp(item->name, item_new->name); \ + if (i == 0) { \ + IConsoleError(type " with this name already exists; insertion aborted"); \ + free(item_new); \ + return; \ + } \ + \ + if (i > 0) break; /* insert at this position */ \ + \ + item_before = item; \ + item = item->next; \ + } \ + \ + if (item_before == NULL) { \ + _base = item_new; \ + } else { \ + item_before->next = item_new; \ + } \ + \ + item_new->next = item; \ + /* END - Alphabetical insert */ \ +} + +/** + * Register a new command to be used in the console + * @param name name of the command that will be used + * @param proc function that will be called upon execution of command + */ +void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc) +{ + char *new_cmd = strdup(name); + IConsoleCmd *item_new = malloc(sizeof(IConsoleCmd)); + + item_new->next = NULL; + item_new->proc = proc; + item_new->name = new_cmd; + + item_new->hook.access = NULL; + item_new->hook.pre = NULL; + item_new->hook.post = NULL; + + IConsoleAddSorted(_iconsole_cmds, item_new, IConsoleCmd, "a command"); +} + +/** + * Find the command pointed to by its string + * @param name command to be found + * @return return Cmdstruct of the found command, or NULL on failure + */ +IConsoleCmd *IConsoleCmdGet(const char *name) +{ + IConsoleCmd *item; + + for (item = _iconsole_cmds; item != NULL; item = item->next) { + if (strcmp(item->name, name) == 0) return item; + } + return NULL; +} + +/** + * Register a an alias for an already existing command in the console + * @param name name of the alias that will be used + * @param cmd name of the command that 'name' will be alias of + */ +void IConsoleAliasRegister(const char *name, const char *cmd) +{ + char *new_alias = strdup(name); + char *cmd_aliased = strdup(cmd); + IConsoleAlias *item_new = malloc(sizeof(IConsoleAlias)); + + item_new->next = NULL; + item_new->cmdline = cmd_aliased; + item_new->name = new_alias; + + IConsoleAddSorted(_iconsole_aliases, item_new, IConsoleAlias, "an alias"); +} + +/** + * Find the alias pointed to by its string + * @param name alias to be found + * @return return Aliasstruct of the found alias, or NULL on failure + */ +IConsoleAlias *IConsoleAliasGet(const char *name) +{ + IConsoleAlias* item; + + for (item = _iconsole_aliases; item != NULL; item = item->next) { + if (strcmp(item->name, name) == 0) return item; + } + + return NULL; +} + +/** copy in an argument into the aliasstream */ +static inline int IConsoleCopyInParams(char *dst, const char *src, uint bufpos) +{ + int len = min(ICON_MAX_STREAMSIZE - bufpos, (uint)strlen(src)); + strncpy(dst, src, len); + + return len; +} + +/** + * An alias is just another name for a command, or for more commands + * Execute it as well. + * @param *alias is the alias of the command + * @param tokencount the number of parameters passed + * @param *tokens are the parameters given to the original command (0 is the first param) + */ +static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT]) +{ + const char *cmdptr; + char *aliases[ICON_MAX_ALIAS_LINES], aliasstream[ICON_MAX_STREAMSIZE]; + uint i; + uint a_index, astream_i; + + memset(&aliases, 0, sizeof(aliases)); + memset(&aliasstream, 0, sizeof(aliasstream)); + + if (_stdlib_con_developer) + IConsolePrintF(_icolour_dbg, "condbg: requested command is an alias; parsing..."); + + aliases[0] = aliasstream; + for (cmdptr = alias->cmdline, a_index = 0, astream_i = 0; *cmdptr != '\0'; cmdptr++) { + if (a_index >= lengthof(aliases) || astream_i >= lengthof(aliasstream)) break; + + switch (*cmdptr) { + case '\'': /* ' will double for "" */ + aliasstream[astream_i++] = '"'; + break; + case ';': /* Cmd seperator, start new command */ + aliasstream[astream_i] = '\0'; + aliases[++a_index] = &aliasstream[++astream_i]; + cmdptr++; + break; + case '%': /* Some or all parameters */ + cmdptr++; + switch (*cmdptr) { + case '+': { /* All parameters seperated: "[param 1]" "[param 2]" */ + for (i = 0; i != tokencount; i++) { + aliasstream[astream_i++] = '"'; + astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); + aliasstream[astream_i++] = '"'; + aliasstream[astream_i++] = ' '; + } + } break; + case '!': { /* Merge the parameters to one: "[param 1] [param 2] [param 3...]" */ + aliasstream[astream_i++] = '"'; + for (i = 0; i != tokencount; i++) { + astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i); + aliasstream[astream_i++] = ' '; + } + aliasstream[astream_i++] = '"'; + + } break; + default: { /* One specific parameter: %A = [param 1] %B = [param 2] ... */ + int param = *cmdptr - 'A'; + + if (param < 0 || param >= tokencount) { + IConsoleError("too many or wrong amount of parameters passed to alias, aborting"); + IConsolePrintF(_icolour_warn, "Usage of alias '%s': %s", alias->name, alias->cmdline); + return; + } + + aliasstream[astream_i++] = '"'; + astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[param], astream_i); + aliasstream[astream_i++] = '"'; + } break; + } break; + + default: + aliasstream[astream_i++] = *cmdptr; + break; + } + } + + for (i = 0; i <= a_index; i++) IConsoleCmdExec(aliases[i]); // execute each alias in turn +} + +/** + * Special function for adding string-type variables. They in addition + * also need a 'size' value saying how long their string buffer is. + * @param size the length of the string buffer + * For more information see @IConsoleVarRegister() + */ +void IConsoleVarStringRegister(const char *name, void *addr, uint32 size, const char *help) +{ + IConsoleVar *var; + IConsoleVarRegister(name, addr, ICONSOLE_VAR_STRING, help); + var = IConsoleVarGet(name); + var->size = size; +} + +/** + * Register a new variable to be used in the console + * @param name name of the variable that will be used + * @param addr memory location the variable will point to + * @param help the help string shown for the variable + * @param type the type of the variable (simple atomic) so we know which values it can get + */ +void IConsoleVarRegister(const char *name, void *addr, IConsoleVarTypes type, const char *help) +{ + char *new_cmd = strdup(name); + IConsoleVar *item_new = malloc(sizeof(IConsoleVar)); + + item_new->help = (help != NULL) ? strdup(help) : NULL; + + item_new->next = NULL; + item_new->name = new_cmd; + item_new->addr = addr; + item_new->proc = NULL; + item_new->type = type; + + item_new->hook.access = NULL; + item_new->hook.pre = NULL; + item_new->hook.post = NULL; + + IConsoleAddSorted(_iconsole_vars, item_new, IConsoleVar, "a variable"); +} + +/** + * Find the variable pointed to by its string + * @param name variable to be found + * @return return Varstruct of the found variable, or NULL on failure + */ +IConsoleVar *IConsoleVarGet(const char *name) +{ + IConsoleVar *item; + for (item = _iconsole_vars; item != NULL; item = item->next) { + if (strcmp(item->name, name) == 0) return item; + } + + return NULL; +} + +/** + * Set a new value to a console variable + * @param *var the variable being set/changed + * @param value the new value given to the variable, cast properly + */ +static void IConsoleVarSetValue(const IConsoleVar *var, uint32 value) +{ + IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); + switch (var->type) { + case ICONSOLE_VAR_BOOLEAN: + *(bool*)var->addr = (value != 0); + break; + case ICONSOLE_VAR_BYTE: + *(byte*)var->addr = (byte)value; + break; + case ICONSOLE_VAR_UINT16: + *(uint16*)var->addr = (uint16)value; + break; + case ICONSOLE_VAR_INT16: + *(int16*)var->addr = (int16)value; + break; + case ICONSOLE_VAR_UINT32: + *(uint32*)var->addr = (uint32)value; + break; + case ICONSOLE_VAR_INT32: + *(int32*)var->addr = (int32)value; + break; + default: NOT_REACHED(); + } + + IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); + IConsoleVarPrintSetValue(var); +} + +/** + * Set a new value to a string-type variable. Basically this + * means to copy the new value over to the container. + * @param *var the variable in question + * @param *value the new value + */ +static void IConsoleVarSetStringvalue(const IConsoleVar *var, const char *value) +{ + if (var->type != ICONSOLE_VAR_STRING || var->addr == NULL) return; + + IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_PRE_ACTION); + ttd_strlcpy(var->addr, value, var->size); + IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_POST_ACTION); + IConsoleVarPrintSetValue(var); // print out the new value, giving feedback + return; +} + +/** + * Query the current value of a variable and return it + * @param *var the variable queried + * @return current value of the variable + */ +static uint32 IConsoleVarGetValue(const IConsoleVar *var) +{ + uint32 result = 0; + + switch (var->type) { + case ICONSOLE_VAR_BOOLEAN: + result = *(bool*)var->addr; + break; + case ICONSOLE_VAR_BYTE: + result = *(byte*)var->addr; + break; + case ICONSOLE_VAR_UINT16: + result = *(uint16*)var->addr; + break; + case ICONSOLE_VAR_INT16: + result = *(int16*)var->addr; + break; + case ICONSOLE_VAR_UINT32: + result = *(uint32*)var->addr; + break; + case ICONSOLE_VAR_INT32: + result = *(int32*)var->addr; + break; + default: NOT_REACHED(); + } + return result; +} + +/** + * Get the value of the variable and put it into a printable + * string form so we can use it for printing + */ +static char *IConsoleVarGetStringValue(const IConsoleVar *var) +{ + static char tempres[50]; + char *value = tempres; + + switch (var->type) { + case ICONSOLE_VAR_BOOLEAN: + snprintf(tempres, sizeof(tempres), "%s", (*(bool*)var->addr) ? "on" : "off"); + break; + case ICONSOLE_VAR_BYTE: + snprintf(tempres, sizeof(tempres), "%u", *(byte*)var->addr); + break; + case ICONSOLE_VAR_UINT16: + snprintf(tempres, sizeof(tempres), "%u", *(uint16*)var->addr); + break; + case ICONSOLE_VAR_UINT32: + snprintf(tempres, sizeof(tempres), "%u", *(uint32*)var->addr); + break; + case ICONSOLE_VAR_INT16: + snprintf(tempres, sizeof(tempres), "%i", *(int16*)var->addr); + break; + case ICONSOLE_VAR_INT32: + snprintf(tempres, sizeof(tempres), "%i", *(int32*)var->addr); + break; + case ICONSOLE_VAR_STRING: + value = (char*)var->addr; + break; + default: NOT_REACHED(); + } + + return value; +} + +/** + * Print out the value of the variable when asked + */ +void IConsoleVarPrintGetValue(const IConsoleVar *var) +{ + char *value; + /* Some variables need really specific handling, handle this in its + * callback function */ + if (var->proc != NULL) { + var->proc(0, NULL); + return; + } + + value = IConsoleVarGetStringValue(var); + IConsolePrintF(_icolour_warn, "Current value for '%s' is: %s", var->name, value); +} + +/** + * Print out the value of the variable after it has been assigned + * a new value, thus giving us feedback on the action + */ +void IConsoleVarPrintSetValue(const IConsoleVar *var) +{ + char *value = IConsoleVarGetStringValue(var); + IConsolePrintF(_icolour_warn, "'%s' changed to: %s", var->name, value); +} + +/** + * Execute a variable command. Without any parameters, print out its value + * with parameters it assigns a new value to the variable + * @param *var the variable that we will be querying/changing + * @param tokencount how many additional parameters have been given to the commandline + * @param *token the actual parameters the variable was called with + */ +void IConsoleVarExec(const IConsoleVar *var, byte tokencount, char *token[ICON_TOKEN_COUNT]) +{ + const char *tokenptr = token[0]; + byte t_index = tokencount; + uint32 value; + + if (_stdlib_con_developer) + IConsolePrintF(_icolour_dbg, "condbg: requested command is a variable"); + + if (tokencount == 0) { /* Just print out value */ + IConsoleVarPrintGetValue(var); + return; + } + + /* Use of assignment sign is not mandatory but supported, so just 'ignore it appropiately' */ + if (strcmp(tokenptr, "=") == 0) tokencount--; + + if (tokencount == 1) { + /* Some variables need really special handling, handle it in their callback procedure */ + if (var->proc != NULL) { + var->proc(tokencount, &token[t_index - tokencount]); // set the new value + return; + } + /* Strings need special processing. No need to convert the argument to + * an integer value, just copy over the argument on a one-by-one basis */ + if (var->type == ICONSOLE_VAR_STRING) { + IConsoleVarSetStringvalue(var, token[t_index - tokencount]); + return; + } else if (GetArgumentInteger(&value, token[t_index - tokencount])) { + IConsoleVarSetValue(var, value); + return; + } + + /* Increase or decrease the value by one. This of course can only happen to 'number' types */ + if (strcmp(tokenptr, "++") == 0 && var->type != ICONSOLE_VAR_STRING) { + IConsoleVarSetValue(var, IConsoleVarGetValue(var) + 1); + return; + } + + if (strcmp(tokenptr, "--") == 0 && var->type != ICONSOLE_VAR_STRING) { + IConsoleVarSetValue(var, IConsoleVarGetValue(var) - 1); + return; + } + } + + IConsoleError("invalid variable assignment"); +} + +/** + * Add a callback function to the variable. Some variables need + * very special processing, which can only be done with custom code + * @param name name of the variable the callback function is added to + * @param proc the function called + */ +void IConsoleVarProcAdd(const char *name, IConsoleCmdProc *proc) +{ + IConsoleVar *var = IConsoleVarGet(name); + if (var == NULL) return; + var->proc = proc; +} + +/** + * Execute a given command passed to us. First chop it up into + * individual tokens (seperated by spaces), then execute it if possible + * @param cmdstr string to be parsed and executed + */ +void IConsoleCmdExec(const char *cmdstr) +{ + IConsoleCmd *cmd = NULL; + IConsoleAlias *alias = NULL; + IConsoleVar *var = NULL; + + const char *cmdptr; + char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE]; + uint t_index, tstream_i; + + bool longtoken = false; + bool foundtoken = false; + + if (cmdstr[0] == '#') return; // comments + + for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) { + if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) { + IConsoleError("command contains malformed characters, aborting"); + IConsolePrintF(_icolour_err, "ERROR: command was: '%s'", cmdstr); + return; + } + } + + if (_stdlib_con_developer) + IConsolePrintF(_icolour_dbg, "condbg: executing cmdline: '%s'", cmdstr); + + memset(&tokens, 0, sizeof(tokens)); + memset(&tokenstream, 0, sizeof(tokenstream)); + + /* 1. Split up commandline into tokens, seperated by spaces, commands + * enclosed in "" are taken as one token. We can only go as far as the amount + * of characters in our stream or the max amount of tokens we can handle */ + for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) { + if (t_index >= lengthof(tokens) || tstream_i >= lengthof(tokenstream)) break; + + switch (*cmdptr) { + case ' ': /* Token seperator */ + if (!foundtoken) break; + + if (longtoken) { + tokenstream[tstream_i] = *cmdptr; + } else { + tokenstream[tstream_i] = '\0'; + foundtoken = false; + } + + tstream_i++; + break; + case '"': /* Tokens enclosed in "" are one token */ + longtoken = !longtoken; + break; + case '\\': /* Escape character for "" */ + if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) { + tokenstream[tstream_i++] = *++cmdptr; + break; + } + /* fallthrough */ + default: /* Normal character */ + tokenstream[tstream_i++] = *cmdptr; + + if (!foundtoken) { + tokens[t_index++] = &tokenstream[tstream_i - 1]; + foundtoken = true; + } + break; + } + } + + if (_stdlib_con_developer) { + uint i; + + for (i = 0; tokens[i] != NULL; i++) { + IConsolePrintF(_icolour_dbg, "condbg: token %d is: '%s'", i, tokens[i]); + } + } + + if (tokens[0] == '\0') return; // don't execute empty commands + /* 2. Determine type of command (cmd, alias or variable) and execute + * First try commands, then aliases, and finally variables. Execute + * the found action taking into account its hooking code + */ + cmd = IConsoleCmdGet(tokens[0]); + if (cmd != NULL) { + if (IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_ACCESS)) { + IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_PRE_ACTION); + if (cmd->proc(t_index, tokens)) { // index started with 0 + IConsoleHookHandle(&cmd->hook, ICONSOLE_HOOK_POST_ACTION); + } else { + cmd->proc(0, NULL); // if command failed, give help + } + } + return; + } + + t_index--; // ignore the variable-name for comfort for both aliases and variaables + alias = IConsoleAliasGet(tokens[0]); + if (alias != NULL) { + IConsoleAliasExec(alias, t_index, &tokens[1]); + return; + } + + var = IConsoleVarGet(tokens[0]); + if (var != NULL) { + if (IConsoleHookHandle(&var->hook, ICONSOLE_HOOK_ACCESS)) { + IConsoleVarExec(var, t_index, &tokens[1]); + } + return; + } + + IConsoleError("command or variable not found"); +} diff --git a/src/console_cmds.c b/src/console_cmds.c deleted file mode 100644 --- a/src/console_cmds.c +++ /dev/null @@ -1,1622 +0,0 @@ -/* $Id$ */ - -#include "stdafx.h" -#include "openttd.h" -#include "console.h" -#include "debug.h" -#include "engine.h" -#include "functions.h" -#include "saveload.h" -#include "string.h" -#include "variables.h" -#include "network/network_data.h" -#include "network/network_client.h" -#include "network/network_server.h" -#include "network/network_udp.h" -#include "command.h" -#include "settings.h" -#include "fios.h" -#include "vehicle.h" -#include "station.h" -#include "strings.h" -#include "screenshot.h" -#include "genworld.h" -#include "date.h" -#include "network/network.h" - -// ** scriptfile handling ** // -static FILE *_script_file; -static bool _script_running; - -// ** console command / variable defines ** // -#define DEF_CONSOLE_CMD(function) static bool function(byte argc, char *argv[]) -#define DEF_CONSOLE_HOOK(function) static bool function(void) - - -/* **************************** */ -/* variable and command hooks */ -/* **************************** */ - -#ifdef ENABLE_NETWORK - -static inline bool NetworkAvailable(void) -{ - if (!_network_available) { - IConsoleError("You cannot use this command because there is no network available."); - return false; - } - return true; -} - -DEF_CONSOLE_HOOK(ConHookServerOnly) -{ - if (!NetworkAvailable()) return false; - - if (!_network_server) { - IConsoleError("This command/variable is only available to a network server."); - return false; - } - return true; -} - -DEF_CONSOLE_HOOK(ConHookClientOnly) -{ - if (!NetworkAvailable()) return false; - - if (_network_server) { - IConsoleError("This command/variable is not available to a network server."); - return false; - } - return true; -} - -DEF_CONSOLE_HOOK(ConHookNeedNetwork) -{ - if (!NetworkAvailable()) return false; - - if (!_networking) { - IConsoleError("Not connected. This command/variable is only available in multiplayer."); - return false; - } - return true; -} - -DEF_CONSOLE_HOOK(ConHookNoNetwork) -{ - if (_networking) { - IConsoleError("This command/variable is forbidden in multiplayer."); - return false; - } - return true; -} - -#endif /* ENABLE_NETWORK */ - -static void IConsoleHelp(const char *str) -{ - IConsolePrintF(_icolour_warn, "- %s", str); -} - -DEF_CONSOLE_CMD(ConResetEngines) -{ - if (argc == 0) { - IConsoleHelp("Reset status data of all engines. This might solve some issues with 'lost' engines. Usage: 'resetengines'"); - return true; - } - - StartupEngines(); - return true; -} - -#ifdef _DEBUG -DEF_CONSOLE_CMD(ConResetTile) -{ - if (argc == 0) { - IConsoleHelp("Reset a tile to bare land. Usage: 'resettile '"); - IConsoleHelp("Tile can be either decimal (34161) or hexadecimal (0x4a5B)"); - return true; - } - - if (argc == 2) { - uint32 result; - if (GetArgumentInteger(&result, argv[1])) { - DoClearSquare((TileIndex)result); - return true; - } - } - - return false; -} - -DEF_CONSOLE_CMD(ConStopAllVehicles) -{ - Vehicle* v; - if (argc == 0) { - IConsoleHelp("Stops all vehicles in the game. For debugging only! Use at your own risk... Usage: 'stopall'"); - return true; - } - - FOR_ALL_VEHICLES(v) { - /* Code ripped from CmdStartStopTrain. Can't call it, because of - * ownership problems, so we'll duplicate some code, for now */ - if (v->type == VEH_Train) - v->u.rail.days_since_order_progr = 0; - v->vehstatus |= VS_STOPPED; - InvalidateWindowWidget(WC_VEHICLE_VIEW, v->index, STATUS_BAR); - InvalidateWindow(WC_VEHICLE_DEPOT, v->tile); - } - return true; -} -#endif /* _DEBUG */ - -DEF_CONSOLE_CMD(ConScrollToTile) -{ - if (argc == 0) { - IConsoleHelp("Center the screen on a given tile. Usage: 'scrollto '"); - IConsoleHelp("Tile can be either decimal (34161) or hexadecimal (0x4a5B)"); - return true; - } - - if (argc == 2) { - uint32 result; - if (GetArgumentInteger(&result, argv[1])) { - if (result >= MapSize()) { - IConsolePrint(_icolour_err, "Tile does not exist"); - return true; - } - ScrollMainWindowToTile((TileIndex)result); - return true; - } - } - - return false; -} - -extern bool SafeSaveOrLoad(const char *filename, int mode, int newgm); -extern void BuildFileList(void); -extern void SetFiosType(const byte fiostype); - -/* Save the map to a file */ -DEF_CONSOLE_CMD(ConSave) -{ - if (argc == 0) { - IConsoleHelp("Save the current game. Usage: 'save '"); - return true; - } - - if (argc == 2) { - char buf[200]; - - snprintf(buf, lengthof(buf), "%s%s%s.sav", _paths.save_dir, PATHSEP, argv[1]); - IConsolePrint(_icolour_def, "Saving map..."); - - if (SaveOrLoad(buf, SL_SAVE) != SL_OK) { - IConsolePrint(_icolour_err, "SaveMap failed"); - } else { - IConsolePrintF(_icolour_def, "Map sucessfully saved to %s", buf); - } - return true; - } - - return false; -} - -/* Explicitly save the configuration */ -DEF_CONSOLE_CMD(ConSaveConfig) -{ - if (argc == 0) { - IConsoleHelp("Saves the current config, typically to 'openttd.cfg'."); - return true; - } - - SaveToConfig(); - IConsolePrint(_icolour_def, "Saved config."); - return true; -} - -static const FiosItem* GetFiosItem(const char* file) -{ - int i; - - _saveload_mode = SLD_LOAD_GAME; - BuildFileList(); - - for (i = 0; i < _fios_num; i++) { - if (strcmp(file, _fios_list[i].name) == 0) break; - if (strcmp(file, _fios_list[i].title) == 0) break; - } - - if (i == _fios_num) { /* If no name matches, try to parse it as number */ - char* endptr; - - i = strtol(file, &endptr, 10); - if (file == endptr || *endptr != '\0') i = -1; - } - - return IS_INT_INSIDE(i, 0, _fios_num) ? &_fios_list[i] : NULL; -} - - -DEF_CONSOLE_CMD(ConLoad) -{ - const FiosItem *item; - const char *file; - - if (argc == 0) { - IConsoleHelp("Load a game by name or index. Usage: 'load '"); - return true; - } - - if (argc != 2) return false; - - file = argv[1]; - item = GetFiosItem(file); - if (item != NULL) { - switch (item->type) { - case FIOS_TYPE_FILE: case FIOS_TYPE_OLDFILE: { - _switch_mode = SM_LOAD; - SetFiosType(item->type); - - ttd_strlcpy(_file_to_saveload.name, FiosBrowseTo(item), sizeof(_file_to_saveload.name)); - ttd_strlcpy(_file_to_saveload.title, item->title, sizeof(_file_to_saveload.title)); - } break; - default: IConsolePrintF(_icolour_err, "%s: Not a savegame.", file); - } - } else { - IConsolePrintF(_icolour_err, "%s: No such file or directory.", file); - } - - FiosFreeSavegameList(); - return true; -} - - -DEF_CONSOLE_CMD(ConRemove) -{ - const FiosItem* item; - const char* file; - - if (argc == 0) { - IConsoleHelp("Remove a savegame by name or index. Usage: 'rm '"); - return true; - } - - if (argc != 2) return false; - - file = argv[1]; - item = GetFiosItem(file); - if (item != NULL) { - if (!FiosDelete(item->name)) - IConsolePrintF(_icolour_err, "%s: Failed to delete file", file); - } else { - IConsolePrintF(_icolour_err, "%s: No such file or directory.", file); - } - - FiosFreeSavegameList(); - return true; -} - - -/* List all the files in the current dir via console */ -DEF_CONSOLE_CMD(ConListFiles) -{ - int i; - - if (argc == 0) { - IConsoleHelp("List all loadable savegames and directories in the current dir via console. Usage: 'ls | dir'"); - return true; - } - - BuildFileList(); - - for (i = 0; i < _fios_num; i++) { - const FiosItem *item = &_fios_list[i]; - IConsolePrintF(_icolour_def, "%d) %s", i, item->title); - } - - FiosFreeSavegameList(); - return true; -} - -/* Change the dir via console */ -DEF_CONSOLE_CMD(ConChangeDirectory) -{ - const FiosItem *item; - const char *file; - - if (argc == 0) { - IConsoleHelp("Change the dir via console. Usage: 'cd '"); - return true; - } - - if (argc != 2) return false; - - file = argv[1]; - item = GetFiosItem(file); - if (item != NULL) { - switch (item->type) { - case FIOS_TYPE_DIR: case FIOS_TYPE_DRIVE: case FIOS_TYPE_PARENT: - FiosBrowseTo(item); - break; - default: IConsolePrintF(_icolour_err, "%s: Not a directory.", file); - } - } else { - IConsolePrintF(_icolour_err, "%s: No such file or directory.", file); - } - - FiosFreeSavegameList(); - return true; -} - -DEF_CONSOLE_CMD(ConPrintWorkingDirectory) -{ - const char *path; - - if (argc == 0) { - IConsoleHelp("Print out the current working directory. Usage: 'pwd'"); - return true; - } - - // XXX - Workaround for broken file handling - FiosGetSavegameList(SLD_LOAD_GAME); - FiosFreeSavegameList(); - - FiosGetDescText(&path, NULL); - IConsolePrint(_icolour_def, path); - return true; -} - -DEF_CONSOLE_CMD(ConClearBuffer) -{ - if (argc == 0) { - IConsoleHelp("Clear the console buffer. Usage: 'clear'"); - return true; - } - - IConsoleClearBuffer(); - InvalidateWindow(WC_CONSOLE, 0); - return true; -} - - -// ********************************* // -// * Network Core Console Commands * // -// ********************************* // -#ifdef ENABLE_NETWORK - -DEF_CONSOLE_CMD(ConBan) -{ - NetworkClientInfo *ci; - const char *banip = NULL; - uint32 index; - - if (argc == 0) { - IConsoleHelp("Ban a player from a network game. Usage: 'ban '"); - IConsoleHelp("For client-id's, see the command 'clients'"); - IConsoleHelp("If the client is no longer online, you can still ban his/her IP"); - return true; - } - - if (argc != 2) return false; - - if (strchr(argv[1], '.') == NULL) { // banning with ID - index = atoi(argv[1]); - ci = NetworkFindClientInfoFromIndex(index); - } else { // banning IP - ci = NetworkFindClientInfoFromIP(argv[1]); - if (ci == NULL) { - banip = argv[1]; - index = (uint32)-1; - } else { - index = ci->client_index; - } - } - - if (index == NETWORK_SERVER_INDEX) { - IConsoleError("Silly boy, you can not ban yourself!"); - return true; - } - - if (index == 0 || (ci == NULL && index != (uint32)-1)) { - IConsoleError("Invalid client"); - return true; - } - - if (ci != NULL) { - banip = inet_ntoa(*(struct in_addr *)&ci->client_ip); - SEND_COMMAND(PACKET_SERVER_ERROR)(NetworkFindClientStateFromIndex(index), NETWORK_ERROR_KICKED); - IConsolePrint(_icolour_def, "Client banned"); - } else { - IConsolePrint(_icolour_def, "Client not online, banned IP"); - } - - /* Add user to ban-list */ - for (index = 0; index < lengthof(_network_ban_list); index++) { - if (_network_ban_list[index] == NULL) { - _network_ban_list[index] = strdup(banip); - break; - } - } - - return true; -} - -DEF_CONSOLE_CMD(ConUnBan) -{ - uint i, index; - - if (argc == 0) { - IConsoleHelp("Unban a player from a network game. Usage: 'unban '"); - IConsoleHelp("For a list of banned IP's, see the command 'banlist'"); - return true; - } - - if (argc != 2) return false; - - index = (strchr(argv[1], '.') == NULL) ? atoi(argv[1]) : 0; - index--; - - for (i = 0; i < lengthof(_network_ban_list); i++) { - if (_network_ban_list[i] == NULL) continue; - - if (strcmp(_network_ban_list[i], argv[1]) == 0 || index == i) { - free(_network_ban_list[i]); - _network_ban_list[i] = NULL; - IConsolePrint(_icolour_def, "IP unbanned."); - return true; - } - } - - IConsolePrint(_icolour_def, "IP not in ban-list."); - return true; -} - -DEF_CONSOLE_CMD(ConBanList) -{ - uint i; - - if (argc == 0) { - IConsoleHelp("List the IP's of banned clients: Usage 'banlist'"); - return true; - } - - IConsolePrint(_icolour_def, "Banlist: "); - - for (i = 0; i < lengthof(_network_ban_list); i++) { - if (_network_ban_list[i] != NULL) - IConsolePrintF(_icolour_def, " %d) %s", i + 1, _network_ban_list[i]); - } - - return true; -} - -DEF_CONSOLE_CMD(ConPauseGame) -{ - if (argc == 0) { - IConsoleHelp("Pause a network game. Usage: 'pause'"); - return true; - } - - if (_pause == 0) { - DoCommandP(0, 1, 0, NULL, CMD_PAUSE); - IConsolePrint(_icolour_def, "Game paused."); - } else { - IConsolePrint(_icolour_def, "Game is already paused."); - } - - return true; -} - -DEF_CONSOLE_CMD(ConUnPauseGame) -{ - if (argc == 0) { - IConsoleHelp("Unpause a network game. Usage: 'unpause'"); - return true; - } - - if (_pause != 0) { - DoCommandP(0, 0, 0, NULL, CMD_PAUSE); - IConsolePrint(_icolour_def, "Game unpaused."); - } else { - IConsolePrint(_icolour_def, "Game is already unpaused."); - } - - return true; -} - -DEF_CONSOLE_CMD(ConRcon) -{ - if (argc == 0) { - IConsoleHelp("Remote control the server from another client. Usage: 'rcon '"); - IConsoleHelp("Remember to enclose the command in quotes, otherwise only the first parameter is sent"); - return true; - } - - if (argc < 3) return false; - - SEND_COMMAND(PACKET_CLIENT_RCON)(argv[1], argv[2]); - return true; -} - -DEF_CONSOLE_CMD(ConStatus) -{ - static const char* const stat_str[] = { - "inactive", - "authorized", - "waiting", - "loading map", - "map done", - "ready", - "active" - }; - - const NetworkClientState *cs; - - if (argc == 0) { - IConsoleHelp("List the status of all clients connected to the server. Usage 'status'"); - return true; - } - - FOR_ALL_CLIENTS(cs) { - int lag = NetworkCalculateLag(cs); - const NetworkClientInfo *ci = DEREF_CLIENT_INFO(cs); - const char* status; - - status = (cs->status < lengthof(stat_str) ? stat_str[cs->status] : "unknown"); - IConsolePrintF(8, "Client #%1d name: '%s' status: '%s' frame-lag: %3d company: %1d IP: %s unique-id: '%s'", - cs->index, ci->client_name, status, lag, - ci->client_playas + (IsValidPlayer(ci->client_playas) ? 1 : 0), - GetPlayerIP(ci), ci->unique_id); - } - - return true; -} - -DEF_CONSOLE_CMD(ConServerInfo) -{ - const NetworkGameInfo *gi; - - if (argc == 0) { - IConsoleHelp("List current and maximum client/player limits. Usage 'server_info'"); - IConsoleHelp("You can change these values by setting the variables 'max_clients', 'max_companies' and 'max_spectators'"); - return true; - } - - gi = &_network_game_info; - IConsolePrintF(_icolour_def, "Current/maximum clients: %2d/%2d", gi->clients_on, gi->clients_max); - IConsolePrintF(_icolour_def, "Current/maximum companies: %2d/%2d", ActivePlayerCount(), gi->companies_max); - IConsolePrintF(_icolour_def, "Current/maximum spectators: %2d/%2d", NetworkSpectatorCount(), gi->spectators_max); - - return true; -} - -DEF_CONSOLE_HOOK(ConHookValidateMaxClientsCount) -{ - /* XXX - hardcoded, string limiation -- TrueLight - * XXX - also see network.c:NetworkStartup ~1356 */ - if (_network_game_info.clients_max > 10) { - _network_game_info.clients_max = 10; - IConsoleError("Maximum clients out of bounds, truncating to limit."); - } - - return true; -} - -DEF_CONSOLE_HOOK(ConHookValidateMaxCompaniesCount) -{ - if (_network_game_info.companies_max > MAX_PLAYERS) { - _network_game_info.companies_max = MAX_PLAYERS; - IConsoleError("Maximum companies out of bounds, truncating to limit."); - } - - return true; -} - -DEF_CONSOLE_HOOK(ConHookValidateMaxSpectatorsCount) -{ - /* XXX @see ConHookValidateMaxClientsCount */ - if (_network_game_info.spectators_max > 10) { - _network_game_info.spectators_max = 10; - IConsoleError("Maximum spectators out of bounds, truncating to limit."); - } - - return true; -} - -DEF_CONSOLE_HOOK(ConHookCheckMinPlayers) -{ - CheckMinPlayers(); - return true; -} - -DEF_CONSOLE_CMD(ConKick) -{ - NetworkClientInfo *ci; - uint32 index; - - if (argc == 0) { - IConsoleHelp("Kick a player from a network game. Usage: 'kick '"); - IConsoleHelp("For client-id's, see the command 'clients'"); - return true; - } - - if (argc != 2) return false; - - if (strchr(argv[1], '.') == NULL) { - index = atoi(argv[1]); - ci = NetworkFindClientInfoFromIndex(index); - } else { - ci = NetworkFindClientInfoFromIP(argv[1]); - index = (ci == NULL) ? 0 : ci->client_index; - } - - if (index == NETWORK_SERVER_INDEX) { - IConsoleError("Silly boy, you can not kick yourself!"); - return true; - } - - if (index == 0) { - IConsoleError("Invalid client"); - return true; - } - - if (ci != NULL) { - SEND_COMMAND(PACKET_SERVER_ERROR)(NetworkFindClientStateFromIndex(index), NETWORK_ERROR_KICKED); - } else { - IConsoleError("Client not found"); - } - - return true; -} - -DEF_CONSOLE_CMD(ConResetCompany) -{ - const Player *p; - const NetworkClientState *cs; - const NetworkClientInfo *ci; - PlayerID index; - - if (argc == 0) { - IConsoleHelp("Remove an idle company from the game. Usage: 'reset_company '"); - IConsoleHelp("For company-id's, see the list of companies from the dropdown menu. Player 1 is 1, etc."); - return true; - } - - if (argc != 2) return false; - - index = atoi(argv[1]) - 1; - - /* Check valid range */ - if (!IsValidPlayer(index)) { - IConsolePrintF(_icolour_err, "Company does not exist. Company-id must be between 1 and %d.", MAX_PLAYERS); - return true; - } - - /* Check if company does exist */ - p = GetPlayer(index); - if (!p->is_active) { - IConsoleError("Company does not exist."); - return true; - } - - if (p->is_ai) { - IConsoleError("Company is owned by an AI."); - return true; - } - - /* Check if the company has active players */ - FOR_ALL_CLIENTS(cs) { - ci = DEREF_CLIENT_INFO(cs); - if (ci->client_playas == index) { - IConsoleError("Cannot remove company: a client is connected to that company."); - return true; - } - } - ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); - if (ci->client_playas == index) { - IConsoleError("Cannot remove company: the server is connected to that company."); - return true; - } - - /* It is safe to remove this company */ - DoCommandP(0, 2, index, NULL, CMD_PLAYER_CTRL); - IConsolePrint(_icolour_def, "Company deleted."); - - return true; -} - -DEF_CONSOLE_CMD(ConNetworkClients) -{ - NetworkClientInfo *ci; - - if (argc == 0) { - IConsoleHelp("Get a list of connected clients including their ID, name, company-id, and IP. Usage: 'clients'"); - return true; - } - - FOR_ALL_ACTIVE_CLIENT_INFOS(ci) { - IConsolePrintF(8, "Client #%1d name: '%s' company: %1d IP: %s", - ci->client_index, ci->client_name, - ci->client_playas + (IsValidPlayer(ci->client_playas) ? 1 : 0), - GetPlayerIP(ci)); - } - - return true; -} - -DEF_CONSOLE_CMD(ConNetworkConnect) -{ - char *ip; - const char *port = NULL; - const char *player = NULL; - uint16 rport; - - if (argc == 0) { - IConsoleHelp("Connect to a remote OTTD server and join the game. Usage: 'connect '"); - IConsoleHelp("IP can contain port and player: 'IP[[#Player]:Port]', eg: 'server.ottd.org#2:443'"); - IConsoleHelp("Player #255 is spectator all others are a certain company with Company 1 being #1"); - return true; - } - - if (argc < 2) return false; - if (_networking) NetworkDisconnect(); // we are in network-mode, first close it! - - ip = argv[1]; - /* Default settings: default port and new company */ - rport = NETWORK_DEFAULT_PORT; - _network_playas = PLAYER_NEW_COMPANY; - - ParseConnectionString(&player, &port, ip); - - IConsolePrintF(_icolour_def, "Connecting to %s...", ip); - if (player != NULL) { - _network_playas = atoi(player); - IConsolePrintF(_icolour_def, " player-no: %d", _network_playas); - - /* From a user pov 0 is a new player, internally it's different and all - * players are offset by one to ease up on users (eg players 1-8 not 0-7) */ - if (_network_playas != PLAYER_SPECTATOR) { - _network_playas--; - if (!IsValidPlayer(_network_playas)) return false; - } - } - if (port != NULL) { - rport = atoi(port); - IConsolePrintF(_icolour_def, " port: %s", port); - } - - NetworkClientConnectGame(ip, rport); - - return true; -} - -#endif /* ENABLE_NETWORK */ - -/* ******************************** */ -/* script file console commands */ -/* ******************************** */ - -DEF_CONSOLE_CMD(ConExec) -{ - char cmdline[ICON_CMDLN_SIZE]; - char *cmdptr; - - if (argc == 0) { - IConsoleHelp("Execute a local script file. Usage: 'exec