@@ -10,9 +10,11 @@
#include "../stdafx.h"
#include "../core/backup_type.hpp"
#include "../core/bitmath_func.hpp"
#include "../core/random_func.hpp"
#include "../company_base.h"
#include "../company_func.h"
#include "../network/network.h"
#include "../saveload/saveload.h"
#include "../window_func.h"
#include "../framerate_type.h"
#include "ai_scanner.hpp"
@@ -26,6 +28,7 @@
/* static */ uint AI::frame_counter = 0;
/* static */ AIScannerInfo *AI::scanner_info = nullptr;
/* static */ AIScannerLibrary *AI::scanner_library = nullptr;
/* static */ uint AI::max_opcodes[MAX_COMPANIES];

/* static */ bool AI::CanStartNew()
{
@@ -55,6 +58,7 @@

c->ai_info = info;
assert(c->ai_instance == nullptr);
AI::SetMaxOpCodes(company, _settings_game.script.script_max_opcode_till_suspend);
c->ai_instance = new AIInstance();
c->ai_instance->Initialize(info);

@@ -114,7 +118,11 @@
cur_company.Restore();

InvalidateWindowData(WC_AI_DEBUG, 0, -1);
DeleteWindowById(WC_AI_SETTINGS, company);

if (AIConfig::GetConfig(company)->IsRandom()) {
AIConfig::GetConfig(company)->Change(nullptr);
}
InvalidateWindowData(WC_AI_SETTINGS, company);
}

/* static */ void AI::Pause(CompanyID company)
@@ -277,7 +285,7 @@

/* static */ void AI::Save(CompanyID company)
{
if (!_networking || _network_server) {
if (!_networking || (_network_server && !_save_empty_script)) {
Company *c = Company::GetIfValid(company);
assert(c != nullptr && c->ai_instance != nullptr);

@@ -304,15 +312,42 @@
}
}

/* static */ int AI::GetStartNextTime()
/* static */ int AI::GetStartNextTime(uint count)
{
int start_delay = _settings_game.ai.ai_start_delay;
if (start_delay > 0 && AI::START_DELAY_DEVIATION != 0) {
/* Add random deviation */
start_delay += InteractiveRandomRange(AI::START_DELAY_DEVIATION * 2 + 1) - AI::START_DELAY_DEVIATION;

/* ai_start_delay = 0 is a special case, where random deviation does not occur.
* If ai_start_delay was not already 0, then a minimum value of 1 must apply. */
start_delay = Clamp(start_delay, max((int)AI::START_DELAY_MIN, 1), AI::START_DELAY_MAX);
}
return start_delay;
}

/* static */ CompanyID AI::GetStartNextCompany(uint count)
{
/* Find the first company which doesn't exist yet */
for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) {
if (!Company::IsValidID(c)) return AIConfig::GetConfig(c, AIConfig::SSS_FORCE_GAME)->GetSetting("start_date");
if (!Company::IsValidID(c)) {
if (count == 0) return c;
count--;
}
}

/* Currently no AI can be started, check again in a year. */
return DAYS_IN_YEAR;
/* Currently no AI can be started. */
return INVALID_COMPANY;
}

/* static */ uint AI::GetMaxOpCodes(CompanyID company)
{
return AI::max_opcodes[company];
}

/* static */ void AI::SetMaxOpCodes(CompanyID company, uint max_opcodes)
{
AI::max_opcodes[company] = max_opcodes;
}

/* static */ char *AI::GetConsoleList(char *p, const char *last, bool newest_only)

Large diffs are not rendered by default.

@@ -14,7 +14,7 @@

Window* ShowAIDebugWindow(CompanyID show_company = INVALID_COMPANY);
void ShowAIConfigWindow();
void ShowAIDebugWindowIfAIError();
void ShowAIDebugWindowIfAIError(Owner owner = INVALID_OWNER);
void InitializeAIGui();

#endif /* AI_GUI_HPP */
@@ -69,11 +69,6 @@ template <> const char *GetClassName<AIInfo, ST_AI>() { return "AIInfo"; }
SQInteger res = ScriptInfo::Constructor(vm, info);
if (res != 0) return res;

ScriptConfigItem config = _start_date_config;
config.name = stredup(config.name);
config.description = stredup(config.description);
info->config_list.push_front(config);

if (info->engine->MethodExists(*info->SQ_instance, "MinVersionToLoad")) {
if (!info->engine->CallIntegerMethod(*info->SQ_instance, "MinVersionToLoad", &info->min_loadable_version, MAX_GET_OPS)) return SQ_ERROR;
} else {
@@ -214,7 +214,7 @@ void AIInstance::Died()
{
ScriptInstance::Died();

ShowAIDebugWindow(_current_company);
ShowAIDebugWindowIfAIError(_current_company);

const AIInfo *info = AIConfig::GetConfig(_current_company, AIConfig::SSS_FORCE_GAME)->GetInfo();
if (info != nullptr) {
@@ -73,6 +73,7 @@ struct AircraftCache {
*/
struct Aircraft FINAL : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
uint16 crashed_counter; ///< Timer for handling crash animations.
uint16 flight_counter; ///< Handler for flight distance since last takeoff.
byte pos; ///< Next desired position of the aircraft.
byte previous_pos; ///< Previous desired position of the aircraft.
StationID targetairport; ///< Airport to go to next.
@@ -134,6 +135,15 @@ struct Aircraft FINAL : public SpecializedVehicle<Aircraft, VEH_AIRCRAFT> {
{
return this->acache.cached_max_range;
}

/** Increase flight_counter everytime the aircraft coordinates don't match. */
void UpdateFlightDistance(Aircraft *v, TileIndex old_pos, TileIndex new_pos)
{
if (old_pos == new_pos) return;
if (v->flight_counter == UINT16_MAX) return;

v->flight_counter = v->flight_counter < UINT16_MAX - 1 ? v->flight_counter + 1 : UINT16_MAX;
}
};

void GetRotorImage(const Aircraft *v, EngineImageType image_type, VehicleSpriteSeq *result);
@@ -410,7 +410,10 @@ bool Aircraft::FindClosestDepot(TileIndex *location, DestinationID *destination,

static void CheckIfAircraftNeedsService(Aircraft *v)
{
if (Company::Get(v->owner)->settings.vehicle.servint_aircraft == 0 || !v->NeedsAutomaticServicing()) return;
bool needs_automatic_servicing = v->NeedsAutomaticServicing();
bool has_pending_replace = v->HasPendingReplace();
if (Company::Get(v->owner)->settings.vehicle.servint_aircraft == 0 || (!needs_automatic_servicing &&
(!has_pending_replace || v->subtype != AIR_HELICOPTER || !_settings_game.order.serviceathelipad))) return;
if (v->IsChainInDepot()) {
VehicleServiceInDepot(v);
return;
@@ -431,6 +434,39 @@ static void CheckIfAircraftNeedsService(Aircraft *v)
} else if (v->current_order.IsType(OT_GOTO_DEPOT)) {
v->current_order.MakeDummy();
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
} else {
bool hangar_in_o = false;
const Order *o;

/* Is there at least an airport coupled with a hangar in the orders? */
FOR_VEHICLE_ORDERS(v, o) {
if (o->IsType(OT_GOTO_STATION)) {
const Station *ost = Station::Get(o->GetDestination());
if (ost->airport.HasHangar() ||
/* Helicopters can be serviced at helipads as long as there is no pending replace and serviceathelipad is enabled */
(v->subtype == AIR_HELICOPTER && !has_pending_replace && _settings_game.order.serviceathelipad && ost->airport.GetFTA()->num_helipads)) {
hangar_in_o = true;
break;
}
}
}

if (!hangar_in_o) {
/* there's no airport coupled with a hangar in the orders, so look for a nearby hangar */
const StationID nearest_hangar = FindNearestHangar(v);

/* v->tile can't be used here, when aircraft is flying v->tile is set to 0 */
TileIndex vtile = TileVirtXY(v->x_pos, v->y_pos);

if (nearest_hangar != INVALID_STATION && ((has_pending_replace && v->subtype == AIR_HELICOPTER && _settings_game.order.serviceathelipad && !needs_automatic_servicing) ||
/* is nearest hangar closer than destination? */
DistanceSquare(vtile, Station::Get(nearest_hangar)->airport.tile) <= DistanceSquare(vtile, st->airport.tile))) {
/* defer destination, service aircraft at that hangar now */
v->current_order.MakeGoToDepot(nearest_hangar, ODTFB_SERVICE);
v->SetDestTile(v->GetOrderStationLocation(nearest_hangar));
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
}
}
}
}

@@ -907,6 +943,7 @@ static bool AircraftController(Aircraft *v)
if (u->cur_speed > 32) {
v->cur_speed = 0;
if (--u->cur_speed == 32) {
v->flight_counter = 0;
if (!PlayVehicleSound(v, VSE_START)) {
SndPlayVehicleFx(SND_18_HELICOPTER, v);
}
@@ -1125,6 +1162,7 @@ static bool AircraftController(Aircraft *v)

}

if (v->state == FLYING || v->state == ENDTAKEOFF || v->state == LANDING || v->state == HELILANDING) v->UpdateFlightDistance(v, TileVirtXY(v->x_pos, v->y_pos), TileVirtXY(gp.x, gp.y));
SetAircraftPosition(v, gp.x, gp.y, z);
} while (--count != 0);
return false;
@@ -1324,17 +1362,17 @@ static void CrashAirplane(Aircraft *v)
*/
static void MaybeCrashAirplane(Aircraft *v)
{
if (_settings_game.vehicle.plane_crashes == 0) return;

Station *st = Station::Get(v->targetairport);

uint32 prob;
uint32 prob = (0x4000 << (_settings_game.vehicle.plane_crashes > 1 ? _settings_game.vehicle.plane_crashes - 1 : _settings_game.vehicle.plane_crashes));
if ((st->airport.GetFTA()->flags & AirportFTAClass::SHORT_STRIP) &&
(AircraftVehInfo(v->engine_type)->subtype & AIR_FAST) &&
!_cheats.no_jetcrash.value) {
prob = 3276;
(AircraftVehInfo(v->engine_type)->subtype & AIR_FAST)) {
prob /= !_cheats.no_jetcrash.value ? 20 : 1500;
} else {
if (_settings_game.vehicle.plane_crashes == 0) return;
prob = (0x4000 << _settings_game.vehicle.plane_crashes) / 1500;
if (_settings_game.vehicle.plane_crashes == 1) return;
prob /= 1500;
}

if (GB(Random(), 0, 22) > prob) return;
@@ -1542,6 +1580,14 @@ static void AircraftEventHandler_AtTerminal(Aircraft *v, const AirportFTAClass *
/* airport-road is free. We either have to go to another airport, or to the hangar
* ---> start moving */

if (Station::Get(v->targetairport)->airport.HasHangar()) {
if (v->NeedsAutomaticServicing() || (v->subtype == AIR_HELICOPTER && v->HasPendingReplace() && _settings_game.order.serviceathelipad &&
(!v->HasDepotOrder() || !(v->current_order.IsType(OT_GOTO_DEPOT) && v->current_order.GetDepotOrderType() != ODTFB_SERVICE)))) {
v->current_order.MakeGoToDepot(v->targetairport, ODTFB_SERVICE);
SetWindowWidgetDirty(WC_VEHICLE_VIEW, v->index, WID_VV_START_STOP);
}
}

bool go_to_hangar = false;
switch (v->current_order.GetType()) {
case OT_GOTO_STATION: // ready to fly to another airport
@@ -1577,6 +1623,7 @@ static void AircraftEventHandler_TakeOff(Aircraft *v, const AirportFTAClass *apc
{
PlayAircraftSound(v); // play takeoffsound for airplanes
v->state = STARTTAKEOFF;
v->flight_counter = 0;
}

static void AircraftEventHandler_StartTakeOff(Aircraft *v, const AirportFTAClass *apc)
@@ -1599,13 +1646,6 @@ static void AircraftEventHandler_HeliTakeOff(Aircraft *v, const AirportFTAClass

/* get the next position to go to, differs per airport */
AircraftNextAirportPos_and_Order(v);

/* Send the helicopter to a hangar if needed for replacement */
if (v->NeedsAutomaticServicing()) {
Backup<CompanyID> cur_company(_current_company, v->owner, FILE_LINE);
DoCommand(v->tile, v->index | DEPOT_SERVICE | DEPOT_LOCATE_HANGAR, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT);
cur_company.Restore();
}
}

static void AircraftEventHandler_Flying(Aircraft *v, const AirportFTAClass *apc)
@@ -1650,13 +1690,6 @@ static void AircraftEventHandler_Landing(Aircraft *v, const AirportFTAClass *apc
{
v->state = ENDLANDING;
AircraftLandAirplane(v); // maybe crash airplane

/* check if the aircraft needs to be replaced or renewed and send it to a hangar if needed */
if (v->NeedsAutomaticServicing()) {
Backup<CompanyID> cur_company(_current_company, v->owner, FILE_LINE);
DoCommand(v->tile, v->index | DEPOT_SERVICE, 0, DC_EXEC, CMD_SEND_VEHICLE_TO_DEPOT);
cur_company.Restore();
}
}

static void AircraftEventHandler_HeliLanding(Aircraft *v, const AirportFTAClass *apc)
@@ -81,11 +81,6 @@ bool CheckAutoreplaceValidity(EngineID from, EngineID to, CompanyID company)
if (HasBit(e_from->info.misc_flags, EF_ROAD_TRAM) != HasBit(e_to->info.misc_flags, EF_ROAD_TRAM)) return false;
break;

case VEH_AIRCRAFT:
/* make sure that we do not replace a plane with a helicopter or vice versa */
if ((e_from->u.air.subtype & AIR_CTOL) != (e_to->u.air.subtype & AIR_CTOL)) return false;
break;

default: break;
}

@@ -31,6 +31,7 @@
#include "safeguards.h"

void DrawEngineList(VehicleType type, int x, int r, int y, const GUIEngineList *eng_list, uint16 min, uint16 max, EngineID selected_id, bool show_count, GroupID selected_group);
int _autoreplace_last_selected_aircraft_type = 1; // Last aircraft type option selected by default (Same aircraft type)

static bool EngineNumberSorter(const EngineID &a, const EngineID &b)
{
@@ -72,6 +73,15 @@ static const StringID _start_replace_dropdown[] = {
INVALID_STRING_ID
};

static const StringID _autoreplace_aircraft_type_dropdown[] = {
STR_REPLACE_ALL_AIRCRAFT_TYPES, // sel_air_type = 0
STR_REPLACE_SAME_AIRCRAFT_TYPE, // sel_air_type = 1
STR_REPLACE_HELICOPTER, // sel_air_type = 2
STR_REPLACE_SMALL_AEROPLANE, // sel_air_type = 3
STR_REPLACE_LARGE_AEROPLANE, // sel_air_type = 4
INVALID_STRING_ID
};

/**
* Window for the autoreplacing of vehicles.
*/
@@ -85,6 +95,7 @@ class ReplaceVehicleWindow : public Window {
byte sort_criteria; ///< Criteria of sorting vehicles.
bool descending_sort_order; ///< Order of sorting vehicles.
bool show_hidden_engines; ///< Whether to show the hidden engines.
int sel_air_type; ///< Type of aircraft selected to list.
RailType sel_railtype; ///< Type of rail tracks selected. #INVALID_RAILTYPE to show all.
RoadType sel_roadtype; ///< Type of road selected. #INVALID_ROADTYPE to show all.
Scrollbar *vscroll[2];
@@ -110,6 +121,40 @@ class ReplaceVehicleWindow : public Window {
return true;
}

/**
* Figure out if an aircraft should be added to the right list, regarding its type.
* @param from The EngineID of the selected aircraft on the left list.
* @param to The EngineID of the aircraft to be added to the right list.
* @return \c true if the engine should be added to the right list, else \c false.
*/
bool GenerateReplaceAircraftList(EngineID from, EngineID to)
{
const Engine *e_from = Engine::Get(from);
const Engine *e_to = Engine::Get(to);

switch (this->sel_air_type + STR_REPLACE_ALL_AIRCRAFT_TYPES) {
case STR_REPLACE_ALL_AIRCRAFT_TYPES: // All aircraft types
return true;

case STR_REPLACE_SAME_AIRCRAFT_TYPE: // Same aircraft type
if ((e_from->u.air.subtype == AIR_CTOL) != (e_to->u.air.subtype == AIR_CTOL)) return false;
if ((e_from->u.air.subtype == AIR_HELI) != (e_to->u.air.subtype == AIR_HELI)) return false;
if ((e_from->u.air.subtype == (AIR_CTOL | AIR_FAST)) != (e_to->u.air.subtype == (AIR_CTOL | AIR_FAST))) return false;
return true;

case STR_REPLACE_HELICOPTER: // Helicopter
return e_to->u.air.subtype == AIR_HELI;

case STR_REPLACE_SMALL_AEROPLANE: // Small aeroplane
return e_to->u.air.subtype == AIR_CTOL;

case STR_REPLACE_LARGE_AEROPLANE: // Large aeroplane
return e_to->u.air.subtype == (AIR_CTOL | AIR_FAST);

default:
return false;
}
}

/**
* Generate an engines list
@@ -150,6 +195,7 @@ class ReplaceVehicleWindow : public Window {
if (num_engines == 0 && EngineReplacementForCompany(Company::Get(_local_company), eid, this->sel_group) == INVALID_ENGINE) continue;
} else {
if (!CheckAutoreplaceValidity(this->sel_engine[0], eid, _local_company)) continue;
if (type == VEH_AIRCRAFT && !this->GenerateReplaceAircraftList(this->sel_engine[0], eid)) continue;
}

list->push_back(eid);
@@ -233,6 +279,7 @@ class ReplaceVehicleWindow : public Window {
this->sel_engine[0] = INVALID_ENGINE;
this->sel_engine[1] = INVALID_ENGINE;
this->show_hidden_engines = _engine_sort_show_hidden_engines[vehicletype];
this->sel_air_type = _autoreplace_last_selected_aircraft_type;

this->CreateNestedTree();
this->vscroll[0] = this->GetScrollbar(WID_RV_LEFT_SCROLLBAR);
@@ -336,6 +383,17 @@ class ReplaceVehicleWindow : public Window {
break;
}

case WID_RV_AIRCRAFT_TYPE_DROPDOWN: {
Dimension d = GetStringBoundingBox(STR_REPLACE_ALL_AIRCRAFT_TYPES);
for (int i = 0; _autoreplace_aircraft_type_dropdown[i] != INVALID_STRING_ID; i++) {
d = maxdim(d, GetStringBoundingBox(_autoreplace_aircraft_type_dropdown[i]));
}
d.width += padding.width;
d.height += padding.height;
*size = maxdim(*size, d);
break;
}

case WID_RV_START_REPLACE: {
Dimension d = GetStringBoundingBox(STR_REPLACE_VEHICLES_START);
for (int i = 0; _start_replace_dropdown[i] != INVALID_STRING_ID; i++) {
@@ -458,6 +516,11 @@ class ReplaceVehicleWindow : public Window {
default: break;
}

if (this->window_number == VEH_AIRCRAFT) {
/* Show the last selected aircraft type in the pulldown menu */
this->GetWidget<NWidgetCore>(WID_RV_AIRCRAFT_TYPE_DROPDOWN)->widget_data = _autoreplace_aircraft_type_dropdown[sel_air_type];
}

this->DrawWidgets();

if (!this->IsShaded()) {
@@ -532,6 +595,11 @@ class ReplaceVehicleWindow : public Window {
DoCommandP(0, GetCompanySettingIndex("company.renew_keep_length"), Company::Get(_local_company)->settings.renew_keep_length ? 0 : 1, CMD_CHANGE_COMPANY_SETTING);
break;

case WID_RV_AIRCRAFT_TYPE_DROPDOWN: { // Aircraft type dropdown menu
ShowDropDownMenu(this, _autoreplace_aircraft_type_dropdown, sel_air_type, WID_RV_AIRCRAFT_TYPE_DROPDOWN, 0, 0);
break;
}

case WID_RV_START_REPLACE: { // Start replacing
if (this->GetWidget<NWidgetLeaf>(widget)->ButtonHit(pt)) {
this->HandleButtonClick(WID_RV_START_REPLACE);
@@ -622,6 +690,17 @@ class ReplaceVehicleWindow : public Window {
break;
}

case WID_RV_AIRCRAFT_TYPE_DROPDOWN: {
int temp = index;
if (temp == sel_air_type) return; // we didn't select a new one. No need to change anything
sel_air_type = temp;
_autoreplace_last_selected_aircraft_type = this->sel_air_type;
this->vscroll[1]->SetPosition(0);
this->engines[1].ForceRebuild();
this->SetDirty();
break;
}

case WID_RV_START_REPLACE:
this->ReplaceClick_StartReplace(index != 0);
break;
@@ -826,6 +905,60 @@ static WindowDesc _replace_vehicle_desc(
_nested_replace_vehicle_widgets, lengthof(_nested_replace_vehicle_widgets)
);

static const NWidgetPart _nested_replace_aircraft_vehicle_widgets[] = {
NWidget(NWID_HORIZONTAL),
NWidget(WWT_CLOSEBOX, COLOUR_GREY),
NWidget(WWT_CAPTION, COLOUR_GREY, WID_RV_CAPTION), SetMinimalSize(433, 14), SetDataTip(STR_REPLACE_VEHICLES_WHITE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
NWidget(WWT_SHADEBOX, COLOUR_GREY),
NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
NWidget(WWT_STICKYBOX, COLOUR_GREY),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_VEHICLES_IN_USE, STR_REPLACE_VEHICLE_VEHICLES_IN_USE_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY),
NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES, STR_REPLACE_VEHICLE_AVAILABLE_VEHICLES_TOOLTIP), SetFill(1, 1), SetMinimalSize(0, 12), SetResize(1, 0),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PANEL, COLOUR_GREY), SetResize(1, 0), EndContainer(),
NWidget(NWID_VERTICAL),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_SORT_ASCENDING_DESCENDING), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_SORT_DROPDOWN), SetResize(1, 0), SetFill(1, 1), SetDataTip(STR_JUST_STRING, STR_TOOLTIP_SORT_CRITERIA),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_RV_SHOW_HIDDEN_ENGINES), SetDataTip(STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN, STR_SHOW_HIDDEN_ENGINES_VEHICLE_TRAIN_TOOLTIP),
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_RV_AIRCRAFT_TYPE_DROPDOWN), SetDataTip(STR_BLACK_STRING, STR_REPLACE_HELP_AIRCRAFT_TYPE_DROPDOWN), SetFill(1, 0), SetResize(1, 0),
EndContainer(),
EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_LEFT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_LEFT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_LEFT_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_LEFT_SCROLLBAR),
NWidget(WWT_MATRIX, COLOUR_GREY, WID_RV_RIGHT_MATRIX), SetMinimalSize(216, 0), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_REPLACE_HELP_RIGHT_ARRAY), SetResize(1, 1), SetScrollbar(WID_RV_RIGHT_SCROLLBAR),
NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_RV_RIGHT_SCROLLBAR),
EndContainer(),
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_LEFT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(),
NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_RIGHT_DETAILS), SetMinimalSize(228, 92), SetResize(1, 0), EndContainer(),
EndContainer(),
NWidget(NWID_HORIZONTAL),
NWidget(NWID_PUSHBUTTON_DROPDOWN, COLOUR_GREY, WID_RV_START_REPLACE), SetMinimalSize(139, 12), SetDataTip(STR_REPLACE_VEHICLES_START, STR_REPLACE_HELP_START_BUTTON),
NWidget(WWT_PANEL, COLOUR_GREY, WID_RV_INFO_TAB), SetMinimalSize(167, 12), SetDataTip(0x0, STR_REPLACE_HELP_REPLACE_INFO_TAB), SetResize(1, 0), EndContainer(),
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_RV_STOP_REPLACE), SetMinimalSize(138, 12), SetDataTip(STR_REPLACE_VEHICLES_STOP, STR_REPLACE_HELP_STOP_BUTTON),
NWidget(WWT_RESIZEBOX, COLOUR_GREY),
EndContainer(),
};

static WindowDesc _replace_aircraft_vehicle_desc(
WDP_AUTO, "replace_vehicle_aircraft", 456, 118,
WC_REPLACE_VEHICLE, WC_NONE,
WDF_CONSTRUCTION,
_nested_replace_aircraft_vehicle_widgets, lengthof(_nested_replace_aircraft_vehicle_widgets)
);

/**
* Show the autoreplace configuration window for a particular group.
* @param id_g The group to replace the vehicles for.
@@ -838,6 +971,7 @@ void ShowReplaceGroupVehicleWindow(GroupID id_g, VehicleType vehicletype)
switch (vehicletype) {
case VEH_TRAIN: desc = &_replace_rail_vehicle_desc; break;
case VEH_ROAD: desc = &_replace_road_vehicle_desc; break;
case VEH_AIRCRAFT: desc = &_replace_aircraft_vehicle_desc; break;
default: desc = &_replace_vehicle_desc; break;
}
new ReplaceVehicleWindow(desc, vehicletype, id_g);
@@ -23,6 +23,7 @@
#include "cmd_helper.h"
#include "tunnelbridge_map.h"
#include "road_gui.h"
#include "viewport_func.h"

#include "widgets/bridge_widget.h"

@@ -60,11 +61,16 @@ typedef GUIList<BuildBridgeData> GUIBridgeList; ///< List of bridges, used in #B
*/
void CcBuildBridge(const CommandCost &result, TileIndex end_tile, uint32 p1, uint32 p2, uint32 cmd)
{
if (result.Failed()) return;
if (_settings_client.sound.confirm) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, end_tile);

TransportType transport_type = Extract<TransportType, 15, 2>(p2);

if (result.Failed()) {
if (transport_type == TRANSPORT_WATER) {
SetRedErrorSquare(_build_tunnel_endtile);
}
return;
}
if (_settings_client.sound.confirm) SndPlayTileFx(SND_27_BLACKSMITH_ANVIL, end_tile);

if (transport_type == TRANSPORT_ROAD) {
DiagDirection end_direction = ReverseDiagDir(GetTunnelBridgeDirection(end_tile));
ConnectRoadToStructure(end_tile, end_direction);
@@ -65,8 +65,8 @@ static int32 ClickMoneyCheat(int32 p1, int32 p2)
*/
static int32 ClickChangeCompanyCheat(int32 p1, int32 p2)
{
while ((uint)p1 < Company::GetPoolSize()) {
if (Company::IsValidID((CompanyID)p1)) {
while ((uint)p1 <= COMPANY_SPECTATOR) {
if (Company::IsValidID((CompanyID)p1) || (p1 == COMPANY_SPECTATOR && _settings_client.gui.start_spectator)) {
SetLocalCompany((CompanyID)p1);
return _local_company;
}
@@ -265,7 +265,7 @@ struct CheatWindow : Window {
SetDParam(0, val + 1);
GetString(buf, STR_CHEAT_CHANGE_COMPANY, lastof(buf));
uint offset = 10 + GetStringBoundingBox(buf).width;
DrawCompanyIcon(_local_company, rtl ? text_right - offset - 10 : text_left + offset, y + icon_y_offset + 2);
if (_local_company != COMPANY_SPECTATOR) DrawCompanyIcon(_local_company, rtl ? text_right - offset - 10 : text_left + offset, y + icon_y_offset + 2);
break;
}

@@ -588,24 +588,40 @@ void StartupCompanies()
_next_competitor_start = 0;
}

/** Start a new competitor company if possible. */
static bool MaybeStartNewCompany()
/** Start new competitor companies if possible. */
void MaybeStartNewCompany()
{
if (_networking && Company::GetNumItems() >= _settings_client.network.max_companies) return false;
/* Count number of total existing companies. */
uint current_companies = (uint)Company::GetNumItems();

/* count number of competitors */
uint n = 0;
/* Count number of existing AI only companies. */
uint current_ais = 0;
for (const Company *c : Company::Iterate()) {
if (c->is_ai) n++;
if (c->is_ai) current_ais++;
}

if (n < (uint)_settings_game.difficulty.max_no_competitors) {
/* Send a command to all clients to start up a new AI.
* Works fine for Multiplayer and Singleplayer */
return DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 16, 0, CMD_COMPANY_CTRL);
/* Compute bitmask of AI companies to start. */
CompanyMask ais_to_start = 0;
for (;;) {
uint count = CountBits(ais_to_start);
if (_networking && current_companies + count >= _settings_client.network.max_companies) break;
if (current_ais + count >= (uint)_settings_game.difficulty.max_no_competitors) break;
if (current_companies + count >= MAX_COMPANIES) break;

CompanyID company = AI::GetStartNextCompany(count);
assert(company != INVALID_COMPANY);
assert(!HasBit(ais_to_start, company));
SetBit(ais_to_start, company);

/* Check if the next AI is also scheduled to start immediately */
if (AI::GetStartNextTime(count + 1) != 0) break;
}

return false;
if (CountBits(ais_to_start) > 0) {
/* Send a command to all clients to start up new AI(s).
* Works fine for Multiplayer and Singleplayer */
DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 4 | ais_to_start << 12, 0, CMD_COMPANY_CTRL);
}
}

/** Initialize the pool of companies. */
@@ -706,19 +722,13 @@ void OnTick_Companies()
}

if (_next_competitor_start == 0) {
/* AI::GetStartNextTime() can return 0. */
_next_competitor_start = max(1, AI::GetStartNextTime() * DAY_TICKS);
/* If GetStartNextTime is zero, wait at least a day to re-evaluate.
* This avoids checking every tick if an AI can be started. */
_next_competitor_start = max(DAY_TICKS, AI::GetStartNextTime() * DAY_TICKS);
}

if (_game_mode != GM_MENU && AI::CanStartNew() && --_next_competitor_start == 0) {
/* Allow multiple AIs to possibly start in the same tick. */
do {
if (!MaybeStartNewCompany()) break;

/* In networking mode, we can only send a command to start but it
* didn't execute yet, so we cannot loop. */
if (_networking) break;
} while (AI::GetStartNextTime() == 0);
MaybeStartNewCompany();
}

_cur_company_tick_index = (_cur_company_tick_index + 1) % MAX_COMPANIES;
@@ -798,19 +808,20 @@ void CompanyAdminRemove(CompanyID company_id, CompanyRemoveReason reason)
* @param tile unused
* @param flags operation to perform
* @param p1 various functionality
* - bits 0..15: CompanyCtrlAction
* - bits 16..23: CompanyID
* - bits 24..31: CompanyRemoveReason (with CCA_DELETE)
* - bits 0..3: CompanyCtrlAction
* - bits 4..11: CompanyID
* - bits 12..27: Bitmask of AI companies to start (with CCA_NEW_AI)
* - bits 12..15: CompanyRemoveReason (with CCA_DELETE)
* @param p2 ClientID
* @param text unused
* @return the cost of this operation or an error
*/
CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
InvalidateWindowData(WC_COMPANY_LEAGUE, 0, 0);
CompanyID company_id = (CompanyID)GB(p1, 16, 8);
CompanyID company_id = (CompanyID)GB(p1, 4, 8);

switch ((CompanyCtrlAction)GB(p1, 0, 16)) {
switch ((CompanyCtrlAction)GB(p1, 0, 4)) {
case CCA_NEW: { // Create a new company
/* This command is only executed in a multiplayer game */
if (!_networking) return CMD_ERROR;
@@ -861,17 +872,30 @@ CommandCost CmdCompanyCtrl(TileIndex tile, DoCommandFlag flags, uint32 p1, uint3
break;
}

case CCA_NEW_AI: { // Make a new AI company
case CCA_NEW_AI: { // Make new AI companies
CompanyMask ais_to_start = GB(p1, 12, 16);
if (CountBits(ais_to_start) == 0) return CMD_ERROR;

if (!(flags & DC_EXEC)) return CommandCost();

if (company_id != INVALID_COMPANY && (company_id >= MAX_COMPANIES || Company::IsValidID(company_id))) return CMD_ERROR;
Company *c = DoStartupNewCompany(true, company_id);
if (c != nullptr) NetworkServerNewCompany(c, nullptr);
/* If a company index is specified, a reload AI is in progress.
* At this point, ensure it can start in the same company index.
* Note that this check must not be done in test mode, because
* in a network game, the command to delete the company is still
* in the queue, Company::IsValidID would be returning true. */
if (company_id != INVALID_COMPANY && (company_id >= MAX_COMPANIES || !HasExactlyOneBit(ais_to_start) || company_id != FindFirstBit(ais_to_start) || Company::IsValidID(company_id))) return CMD_ERROR;

uint company;
FOR_EACH_SET_BIT(company, ais_to_start) {
if (company_id != INVALID_COMPANY) assert(company == company_id);
Company *c = DoStartupNewCompany(true, (CompanyID)company);
if (c != nullptr) NetworkServerNewCompany(c, nullptr);
}
break;
}

case CCA_DELETE: { // Delete a company
CompanyRemoveReason reason = (CompanyRemoveReason)GB(p1, 24, 8);
CompanyRemoveReason reason = (CompanyRemoveReason)GB(p1, 12, 4);
if (reason >= CRR_END) return CMD_ERROR;

Company *c = Company::GetIfValid(company_id);
@@ -15,6 +15,7 @@
#include "gfx_type.h"
#include "vehicle_type.h"

void MaybeStartNewCompany();
bool MayCompanyTakeOver(CompanyID cbig, CompanyID small);
void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner);
void GetNameOfOwner(Owner owner, TileIndex tile);
@@ -2333,8 +2333,12 @@ struct CompanyWindow : Window
reinit = true;
}

/* Multiplayer buttons. */
plane = ((!_networking) ? (int)SZSP_NONE : (int)(local ? CWP_MP_C_PWD : CWP_MP_C_JOIN));
if (!_networking) {
plane = (int)((!c->is_ai && !local && _local_company == COMPANY_SPECTATOR) ? CWP_MP_C_JOIN : SZSP_NONE);
} else {
/* Multiplayer buttons. */
plane = (int)(local ? CWP_MP_C_PWD : CWP_MP_C_JOIN);
}
wi = this->GetWidget<NWidgetStacked>(WID_C_SELECT_MULTIPLAYER);
if (plane != wi->shown_plane) {
wi->SetDisplayedPlane(plane);
@@ -2609,6 +2613,11 @@ struct CompanyWindow : Window
break;

case WID_C_COMPANY_JOIN: {
if (!_networking) {
if (_local_company == COMPANY_SPECTATOR) SetLocalCompany((CompanyID)this->window_number);
break;
}

this->query_widget = WID_C_COMPANY_JOIN;
CompanyID company = (CompanyID)this->window_number;
if (_network_server) {
@@ -2681,8 +2690,8 @@ struct CompanyWindow : Window

/* If all shares are owned by someone (none by nobody), disable buy button */
this->SetWidgetDisabledState(WID_C_BUY_SHARE, GetAmountOwnedBy(c, INVALID_OWNER) == 0 ||
/* Only 25% left to buy. If the company is human, disable buying it up.. TODO issues! */
(GetAmountOwnedBy(c, INVALID_OWNER) == 1 && !c->is_ai) ||
/* Only 25% left to buy. If the company is human or AI in multiplayer, disable buying it up! */
(GetAmountOwnedBy(c, INVALID_OWNER) == 1 && (!c->is_ai || (c->is_ai && _networking))) ||
/* Spectators cannot do anything of course */
_local_company == COMPANY_SPECTATOR);

@@ -844,7 +844,7 @@ DEF_CONSOLE_CMD(ConResetCompany)
}

/* It is safe to remove this company */
DoCommandP(0, CCA_DELETE | index << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_DELETE | index << 4 | CRR_MANUAL << 12, 0, CMD_COMPANY_CTRL);
IConsolePrint(CC_DEFAULT, "Company deleted.");

return true;
@@ -1181,8 +1181,14 @@ DEF_CONSOLE_CMD(ConStartAI)
}
}

CompanyID company_id = AI::GetStartNextCompany();
if (company_id == INVALID_COMPANY) {
IConsoleWarning("Can't start a new AI (no more free slots).");
return true;
}

/* Start a new AI company */
DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 16, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_NEW_AI | INVALID_COMPANY << 4 | (1 << company_id) << 12, 0, CMD_COMPANY_CTRL);

return true;
}
@@ -1217,8 +1223,8 @@ DEF_CONSOLE_CMD(ConReloadAI)
}

/* First kill the company of the AI, then start a new one. This should start the current AI again */
DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0,CMD_COMPANY_CTRL);
DoCommandP(0, CCA_NEW_AI | company_id << 16, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_DELETE | company_id << 4 | CRR_MANUAL << 12, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_NEW_AI | company_id << 4 | (1 << company_id) << 12, 0, CMD_COMPANY_CTRL);
IConsolePrint(CC_DEFAULT, "AI reloaded.");

return true;
@@ -1254,7 +1260,7 @@ DEF_CONSOLE_CMD(ConStopAI)
}

/* Now kill the company of the AI. */
DoCommandP(0, CCA_DELETE | company_id << 16 | CRR_MANUAL << 24, 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_DELETE | company_id << 4 | CRR_MANUAL << 12, 0, CMD_COMPANY_CTRL);
IConsolePrint(CC_DEFAULT, "AI stopped, company deleted.");

return true;
@@ -285,19 +285,20 @@ void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
* the client. This is needed as it needs to know whether "you" really
* are the current local company. */
Backup<CompanyID> cur_company(_current_company, old_owner, FILE_LINE);
/* In all cases, make spectators of clients connected to that company */
if (_networking) NetworkClientsToSpectators(old_owner);
if (_networking) {
if (Company::IsValidHumanID(new_owner) && Company::Get(new_owner)->settings.merge_players) {
/* Move clients to the new company */
NetworkClientsToCompany(old_owner, new_owner);
} else {
/* Make spectators of clients connected to that company */
NetworkClientsToSpectators(old_owner);
}
}
if (old_owner == _local_company) {
/* Single player cheated to AI company.
* There are no spectators in single player, so we must pick some other company. */
assert(!_networking);
/* Single player company bankrupts. Move player to Spectator. */
assert(!_networking && _settings_client.gui.start_spectator);
Backup<CompanyID> cur_company2(_current_company, FILE_LINE);
for (const Company *c : Company::Iterate()) {
if (c->index != old_owner) {
SetLocalCompany(c->index);
break;
}
}
SetLocalCompany(COMPANY_SPECTATOR);
cur_company2.Restore();
assert(old_owner != _local_company);
}
@@ -604,11 +605,12 @@ static void CompanyCheckBankrupt(Company *c)
* after 9 months (if it still had value after 6 months) */
default:
case 10: {
if (!_networking && _local_company == c->index) {
/* If we are in offline mode, leave the company playing. Eg. there
* is no THE-END, otherwise mark the client as spectator to make sure
* he/she is no long in control of this company. However... when you
* join another company (cheat) the "unowned" company can bankrupt. */
if (!_networking && _local_company == c->index && !_settings_client.gui.start_spectator) {
/* If we are in offline mode and if spectator mode isn't active,
* leave the company playing. Eg. there is no THE-END, otherwise
* mark the client as spectator to make sure he/she is no long
* in control of this company. However... when you join another
* company (cheat) the "unowned" company can bankrupt. */
c->bankrupt_asked = MAX_UVALUE(CompanyMask);
break;
}
@@ -620,10 +622,10 @@ static void CompanyCheckBankrupt(Company *c)
* updating the local company triggers an assert later on. In the
* case of a network game the command will be processed at a time
* that changing the current company is okay. In case of single
* player we are sure (the above check) that we are not the local
* company and thus we won't be moved. */
* player we ensure that the local company remains the same until
* the end of StateGameLoop, where it could be changed. */
if (!_networking || _network_server) {
DoCommandP(0, CCA_DELETE | (c->index << 16) | (CRR_BANKRUPT << 24), 0, CMD_COMPANY_CTRL);
DoCommandP(0, CCA_DELETE | (c->index << 4) | (CRR_BANKRUPT << 12), 0, CMD_COMPANY_CTRL);
return;
}
break;
@@ -641,7 +643,11 @@ static void CompaniesGenStatistics()
{
/* Check for bankruptcy each month */
for (Company *c : Company::Iterate()) {
Backup<CompanyID> cur_company(_current_company, FILE_LINE);
Backup<CompanyID> loc_company(_local_company, FILE_LINE);
CompanyCheckBankrupt(c);
loc_company.Restore();
cur_company.Restore();
}

Backup<CompanyID> cur_company(_current_company, FILE_LINE);
@@ -1006,6 +1012,19 @@ Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, C
/** The industries we've currently brought cargo to. */
static SmallIndustryList _cargo_delivery_destinations;

static bool CanDeliverGoodsToIndustry(uint cargo_index, CargoID cargo_type, IndustryID source, Industry *ind)
{
if (ind->index == source) return false;

/* Check if matching cargo has been found */
if (cargo_index >= lengthof(ind->accepts_cargo)) return false;

/* Check if industry temporarily refuses acceptance */
if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) return false;

return min(1, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]) != 0;
}

/**
* Transfer goods from station to industry.
* All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo.
@@ -1018,42 +1037,46 @@ static SmallIndustryList _cargo_delivery_destinations;
*/
static uint DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, uint num_pieces, IndustryID source, CompanyID company)
{
/* Find the nearest industrytile to the station sign inside the catchment area, whose industry accepts the cargo.
/* Find a random industry near the station sign inside the catchment area, whose industry accepts the cargo.
* This fails in three cases:
* 1) The station accepts the cargo because there are enough houses around it accepting the cargo.
* 2) The industries in the catchment area temporarily reject the cargo, and the daily station loop has not yet updated station acceptance.
* 3) The results of callbacks CBID_INDUSTRY_REFUSE_CARGO and CBID_INDTILE_CARGO_ACCEPTANCE are inconsistent. (documented behaviour)
*/

uint accepted = 0;

for (Industry *ind : st->industries_near) {
if (num_pieces == 0) break;

if (ind->index == source) continue;
if (st->industries_near.size() == 0) return accepted;

SmallIndustryList rejected;
do {
uint r = RandomRange((uint)st->industries_near.size());
Industry *ind;
for (Industry *i : st->industries_near) {
ind = i;
if (r-- == 0) break;
}

uint cargo_index;
for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
if (cargo_type == ind->accepts_cargo[cargo_index]) break;
}
/* Check if matching cargo has been found */
if (cargo_index >= lengthof(ind->accepts_cargo)) continue;

/* Check if industry temporarily refuses acceptance */
if (IndustryTemporarilyRefusesCargo(ind, cargo_type)) continue;
if (!CanDeliverGoodsToIndustry(cargo_index, cargo_type, source, ind)) {
rejected.emplace_back(ind);
continue;
}

/* Insert the industry into _cargo_delivery_destinations, if not yet contained */
include(_cargo_delivery_destinations, ind);

uint amount = min(num_pieces, 0xFFFFU - ind->incoming_cargo_waiting[cargo_index]);
ind->incoming_cargo_waiting[cargo_index] += amount;
ind->incoming_cargo_waiting[cargo_index]++;
ind->last_cargo_accepted_at[cargo_index] = _date;
num_pieces -= amount;
accepted += amount;
num_pieces--;
accepted++;

/* Update the cargo monitor. */
AddCargoDelivery(cargo_type, company, amount, ST_INDUSTRY, source, st, ind->index);
}
AddCargoDelivery(cargo_type, company, 1, ST_INDUSTRY, source, st, ind->index);
} while (num_pieces != 0 && rejected.size() != st->industries_near.size());

return accepted;
}
@@ -1098,7 +1121,7 @@ static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID dest, Ti
st->town->received[cs->town_effect].new_act += accepted_total;

/* Determine profit */
Money profit = GetTransportedGoodsIncome(accepted_total, DistanceManhattan(source_tile, st->xy), days_in_transit, cargo_type);
Money profit = GetTransportedGoodsIncome(accepted_total, DistanceTransportedGoodsIncome(source_tile, st->xy), days_in_transit, cargo_type);

/* Update the cargo monitor. */
AddCargoDelivery(cargo_type, company->index, accepted_total - accepted_ind, src_type, src, st);
@@ -1220,7 +1243,7 @@ Money CargoPayment::PayTransfer(const CargoPacket *cp, uint count)
count,
/* pay transfer vehicle the difference between the payment for the journey from
* the source to the current point, and the sum of the previous transfer payments */
DistanceManhattan(cp->SourceStationXY(), Station::Get(this->current_station)->xy),
DistanceTransportedGoodsIncome(cp->SourceStationXY(), Station::Get(this->current_station)->xy),
cp->DaysInTransit(),
this->ct);

@@ -2019,7 +2042,7 @@ CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1,
if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 0) return cost;

if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 1) {
if (!c->is_ai) return cost; // We can not buy out a real company (temporarily). TODO: well, enable it obviously.
if (!c->is_ai || (c->is_ai && _networking)) return cost; // We can not buy out a real or AI company in multiplayer.

if (GetAmountOwnedBy(c, _current_company) == 3 && !MayCompanyTakeOver(_current_company, target_company)) return_cmd_error(STR_ERROR_TOO_MANY_VEHICLES_IN_GAME);
}
@@ -40,4 +40,6 @@ extern EngList_SortTypeFunction * const _engine_sort_functions[][11];
uint GetEngineListHeight(VehicleType type);
void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, int button);

extern int _autoreplace_last_selected_aircraft_type;

#endif /* ENGINE_GUI_H */
@@ -20,9 +20,13 @@
#include "guitimer_func.h"
#include "company_base.h"
#include "ai/ai_info.hpp"
#include "ai/ai.hpp"
#include "ai/ai_instance.hpp"
#include "game/game_info.hpp"
#include "game/game.hpp"
#include "game/game_instance.hpp"
#include "settings_func.h"
#include "settings_internal.h"

#include "widgets/framerate_widget.h"
#include "safeguards.h"
@@ -248,6 +252,40 @@ PerformanceMeasurer::~PerformanceMeasurer()
}
}
_pf_data[this->elem].Add(this->start_time, GetPerformanceTimer());

if (!_settings_game.script.self_regulate_max_opcode) return;

/* Self-adjust max opcodes for active scripts */
if (this->elem >= PFE_GAMESCRIPT && this->elem <= PFE_AI14) {
uint active_scripts = Game::GetInstance() != nullptr && !Game::GetInstance()->IsDead() && !Game::GetInstance()->IsPaused();
for (const Company *c : Company::Iterate()) {
if (_pause_mode == PM_UNPAUSED && Company::IsValidAiID(c->index) && Company::Get(c->index)->ai_instance != nullptr && !Company::Get(c->index)->ai_instance->IsDead() && !Company::Get(c->index)->ai_instance->IsPaused()) {
active_scripts++;
}
}

if (active_scripts != 0 && (this->elem == PFE_GAMESCRIPT ? Game::GetInstance() != nullptr && !Game::GetInstance()->IsDead() && !Game::GetInstance()->IsPaused() : _pause_mode == PM_UNPAUSED && Company::IsValidAiID((CompanyID)(this->elem - PFE_AI0)) && Company::Get((CompanyID)(this->elem - PFE_AI0))->ai_instance != nullptr && !Company::Get((CompanyID)(this->elem - PFE_AI0))->ai_instance->IsDead() && !Company::Get((CompanyID)(this->elem - PFE_AI0))->ai_instance->IsPaused())) {
uint dummy; // unused
const SettingDesc *sd = GetSettingFromName("script_max_opcode_till_suspend", &dummy);
assert(sd != nullptr);
uint opcodes = this->elem == PFE_GAMESCRIPT ? Game::GetMaxOpCodes() : AI::GetMaxOpCodes((CompanyID)(this->elem - PFE_AI0));
uint value = opcodes;
double avg = min(9999.99, _pf_data[this->elem].GetAverageDurationMilliseconds(GL_RATE));
double all = min(9999.99, _pf_data[PFE_ALLSCRIPTS].GetAverageDurationMilliseconds(GL_RATE));
if (avg * active_scripts > MILLISECONDS_PER_TICK && all > MILLISECONDS_PER_TICK) {
value = Clamp(opcodes - (avg * active_scripts - MILLISECONDS_PER_TICK) * (avg * active_scripts - MILLISECONDS_PER_TICK), sd->desc.min, GetGameSettings().script.script_max_opcode_till_suspend);
} else if (avg > 0 && avg < MILLISECONDS_PER_TICK / 3 || all < MILLISECONDS_PER_TICK / 3) {
value = Clamp(opcodes + MILLISECONDS_PER_TICK / 3 - avg, sd->desc.min, GetGameSettings().script.script_max_opcode_till_suspend);
}
if (value != opcodes) {
if (this->elem == PFE_GAMESCRIPT) {
Game::SetMaxOpCodes(value);
} else {
AI::SetMaxOpCodes((CompanyID)(this->elem - PFE_AI0), value);
}
}
}
}
}

/** Set the rate of expected cycles per second of a performance element. */
@@ -344,6 +382,12 @@ static const char * GetAIName(int ai_index)
return Company::Get(ai_index)->ai_info->GetName();
}

static const char * GetGSName()
{
if (Game::GetInstance() == NULL) return "";
return Game::GetInfo()->GetName();
}

/** @hideinitializer */
static const NWidgetPart _framerate_window_widgets[] = {
NWidget(NWID_HORIZONTAL),
@@ -369,6 +413,7 @@ static const NWidgetPart _framerate_window_widgets[] = {
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_FRW_SEL_MEMORY),
NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_ALLOCSIZE), SetScrollbar(WID_FRW_SCROLLBAR),
EndContainer(),
NWidget(WWT_EMPTY, COLOUR_GREY, WID_FRW_OPCODES), SetScrollbar(WID_FRW_SCROLLBAR),
EndContainer(),
NWidget(WWT_TEXT, COLOUR_GREY, WID_FRW_INFO_DATA_POINTS), SetDataTip(STR_FRAMERATE_DATA_POINTS, 0x0),
EndContainer(),
@@ -548,12 +593,17 @@ struct FramerateWindow : Window {
for (PerformanceElement e : DISPLAY_ORDER_PFE) {
if (_pf_data[e].num_valid == 0) continue;
Dimension line_size;
if (e < PFE_AI0) {
if (e < PFE_GAMESCRIPT || e > PFE_AI14) {
line_size = GetStringBoundingBox(STR_FRAMERATE_GAMELOOP + e);
} else {
SetDParam(0, e - PFE_AI0 + 1);
SetDParamStr(1, GetAIName(e - PFE_AI0));
line_size = GetStringBoundingBox(STR_FRAMERATE_AI);
if (e == PFE_GAMESCRIPT) {
SetDParamStr(0, GetGSName());
line_size = GetStringBoundingBox(STR_FRAMERATE_GAMESCRIPT);
} else {
SetDParam(0, e - PFE_AI0 + 1);
SetDParamStr(1, GetAIName(e - PFE_AI0));
line_size = GetStringBoundingBox(STR_FRAMERATE_AI);
}
}
size->width = max(size->width, line_size.width);
}
@@ -573,6 +623,24 @@ struct FramerateWindow : Window {
resize->height = FONT_HEIGHT_NORMAL;
break;
}

case WID_FRW_OPCODES: {
size->width = 0;
bool any_active = _pf_data[PFE_GAMESCRIPT].num_valid > 0;
for (uint pfe = PFE_AI0; pfe < PFE_MAX; pfe++) any_active |= _pf_data[pfe].num_valid > 0;
Dimension item_size;
item_size.width = 0;
if (any_active) {
*size = GetStringBoundingBox(STR_FRAMERATE_OPCODES);
SetDParamMaxDigits(0, 6);
item_size = GetStringBoundingBox(STR_FRAMERATE_OPS);
}
size->width = max(size->width, item_size.width);
size->height += FONT_HEIGHT_NORMAL * MIN_ELEMENTS + VSPACING;
resize->width = 0;
resize->height = FONT_HEIGHT_NORMAL;
break;
}
}
}

@@ -630,6 +698,34 @@ struct FramerateWindow : Window {
}
}

void DrawElementMaxOpsColumn(const Rect &r) const
{
const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
uint16 skip = sb->GetPosition();
int drawable = this->num_displayed;
int y = r.top;
DrawString(r.left, r.right, y, STR_FRAMERATE_OPCODES, TC_FROMSTRING, SA_CENTER, true);
y += FONT_HEIGHT_NORMAL + VSPACING;
for (PerformanceElement e : DISPLAY_ORDER_PFE) {
if (_pf_data[e].num_valid == 0) continue;
if (skip > 0) {
skip--;
} else if (e == PFE_GAMESCRIPT || e >= PFE_AI0) {
uint value = e == PFE_GAMESCRIPT ? Game::GetMaxOpCodes() : AI::GetMaxOpCodes((CompanyID)(e - PFE_AI0));
SetDParam(0, value);
DrawString(r.left, r.right, y, STR_FRAMERATE_OPS, TC_FROMSTRING, SA_RIGHT);
y += FONT_HEIGHT_NORMAL;
drawable--;
if (drawable == 0) break;
} else {
/* skip non-script */
y += FONT_HEIGHT_NORMAL;
drawable--;
if (drawable == 0) break;
}
}
}

void DrawWidget(const Rect &r, int widget) const override
{
switch (widget) {
@@ -644,12 +740,17 @@ struct FramerateWindow : Window {
if (skip > 0) {
skip--;
} else {
if (e < PFE_AI0) {
if (e < PFE_GAMESCRIPT || e > PFE_AI14) {
DrawString(r.left, r.right, y, STR_FRAMERATE_GAMELOOP + e, TC_FROMSTRING, SA_LEFT);
} else {
SetDParam(0, e - PFE_AI0 + 1);
SetDParamStr(1, GetAIName(e - PFE_AI0));
DrawString(r.left, r.right, y, STR_FRAMERATE_AI, TC_FROMSTRING, SA_LEFT);
if (e == PFE_GAMESCRIPT) {
SetDParamStr(0, GetGSName());
DrawString(r.left, r.right, y, STR_FRAMERATE_GAMESCRIPT, TC_FROMSTRING, SA_LEFT);
} else {
SetDParam(0, e - PFE_AI0 + 1);
SetDParamStr(1, GetAIName(e - PFE_AI0));
DrawString(r.left, r.right, y, STR_FRAMERATE_AI, TC_FROMSTRING, SA_LEFT);
}
}
y += FONT_HEIGHT_NORMAL;
drawable--;
@@ -669,6 +770,9 @@ struct FramerateWindow : Window {
case WID_FRW_ALLOCSIZE:
DrawElementAllocationsColumn(r);
break;
case WID_FRW_OPCODES:
DrawElementMaxOpsColumn(r);
break;
}
}

@@ -677,7 +781,8 @@ struct FramerateWindow : Window {
switch (widget) {
case WID_FRW_TIMES_NAMES:
case WID_FRW_TIMES_CURRENT:
case WID_FRW_TIMES_AVERAGE: {
case WID_FRW_TIMES_AVERAGE:
case WID_FRW_OPCODES: {
/* Open time graph windows when clicking detail measurement lines */
const Scrollbar *sb = this->GetScrollbar(WID_FRW_SCROLLBAR);
int line = sb->GetScrolledRowFromWidget(pt.y - FONT_HEIGHT_NORMAL - VSPACING, this, widget, VSPACING, FONT_HEIGHT_NORMAL);
@@ -749,12 +854,19 @@ struct FrametimeGraphWindow : Window {
{
switch (widget) {
case WID_FGW_CAPTION:
if (this->element < PFE_AI0) {
if (this->element < PFE_GAMESCRIPT) {
SetDParam(0, STR_FRAMETIME_CAPTION_GAMELOOP + this->element);
} else {
SetDParam(0, STR_FRAMETIME_CAPTION_AI);
SetDParam(1, this->element - PFE_AI0 + 1);
SetDParamStr(2, GetAIName(this->element - PFE_AI0));
if (this->element == PFE_GAMESCRIPT) {
SetDParam(0, STR_FRAMETIME_CAPTION_GS_MAXOPCODE);
SetDParamStr(1, GetGSName());
SetDParam(2, Game::GetMaxOpCodes());
} else {
SetDParam(0, STR_FRAMETIME_CAPTION_AI_MAXOPCODE);
SetDParam(1, this->element - PFE_AI0 + 1);
SetDParamStr(2, GetAIName(this->element - PFE_AI0));
SetDParam(3, AI::GetMaxOpCodes((CompanyID)(this->element - PFE_AI0)));
}
}
break;
}
@@ -1064,12 +1176,22 @@ void ConPrintFramerate()
seprintf(ai_name_buf, lastof(ai_name_buf), "AI %d %s", e - PFE_AI0 + 1, GetAIName(e - PFE_AI0)),
name = ai_name_buf;
}
IConsolePrintF(TC_LIGHT_BLUE, "%s times: %.2fms %.2fms %.2fms",
name,
pf.GetAverageDurationMilliseconds(count1),
pf.GetAverageDurationMilliseconds(count2),
pf.GetAverageDurationMilliseconds(count3));
printed_anything = true;
if (e >= PFE_GAMESCRIPT && e <= PFE_AI14) {
IConsolePrintF(TC_LIGHT_BLUE, "%s times: %.2fms %.2fms %.2fms opcodes: %d",
name,
pf.GetAverageDurationMilliseconds(count1),
pf.GetAverageDurationMilliseconds(count2),
pf.GetAverageDurationMilliseconds(count3),
e == PFE_GAMESCRIPT ? Game::GetMaxOpCodes() : AI::GetMaxOpCodes((CompanyID)(e - PFE_AI0)));
printed_anything = true;
} else {
IConsolePrintF(TC_LIGHT_BLUE, "%s times: %.2fms %.2fms %.2fms",
name,
pf.GetAverageDurationMilliseconds(count1),
pf.GetAverageDurationMilliseconds(count2),
pf.GetAverageDurationMilliseconds(count3));
printed_anything = true;
}
}

if (!printed_anything) {
@@ -92,6 +92,16 @@ class Game {
*/
static void Load(int version);

/**
* Get the current maximum number of opcodes for a GameScript before it's suspended.
*/
static uint GetMaxOpCodes();

/**
* Set a maximum number of opcodes for a GameScript before it's suspended.
*/
static void SetMaxOpCodes(uint max_opcodes);

/** Wrapper function for GameScanner::GetConsoleList */
static char *GetConsoleList(char *p, const char *last, bool newest_only = false);
/** Wrapper function for GameScanner::GetConsoleLibraryList */
@@ -124,6 +134,7 @@ class Game {
static class GameScannerInfo *scanner_info; ///< Scanner for Game scripts.
static class GameScannerLibrary *scanner_library; ///< Scanner for GS Libraries.
static class GameInfo *info; ///< Current selected GameInfo.
static uint max_opcodes; ///< Maximum number of opcodes before the GameScript is suspended
};

#endif /* GAME_HPP */
@@ -12,6 +12,7 @@
#include "../company_base.h"
#include "../company_func.h"
#include "../network/network.h"
#include "../saveload/saveload.h"
#include "../window_func.h"
#include "../framerate_type.h"
#include "game.hpp"
@@ -27,6 +28,7 @@
/* static */ GameInstance *Game::instance = nullptr;
/* static */ GameScannerInfo *Game::scanner_info = nullptr;
/* static */ GameScannerLibrary *Game::scanner_library = nullptr;
/* static */ uint Game::max_opcodes = 0;

/* static */ void Game::GameLoop()
{
@@ -86,6 +88,7 @@
cur_company.Change(OWNER_DEITY);

Game::info = info;
Game::SetMaxOpCodes(_settings_game.script.script_max_opcode_till_suspend);
Game::instance = new GameInstance();
Game::instance->Initialize(info);

@@ -204,7 +207,7 @@

/* static */ void Game::Save()
{
if (Game::instance != nullptr && (!_networking || _network_server)) {
if (Game::instance != nullptr && (!_networking || (_network_server && !_save_empty_script))) {
Backup<CompanyID> cur_company(_current_company, OWNER_DEITY, FILE_LINE);
Game::instance->Save();
cur_company.Restore();
@@ -225,6 +228,16 @@
}
}

/* static */ uint Game::GetMaxOpCodes()
{
return Game::max_opcodes;
}

/* static */ void Game::SetMaxOpCodes(uint max_opcodes)
{
Game::max_opcodes = max_opcodes;
}

/* static */ char *Game::GetConsoleList(char *p, const char *last, bool newest_only)
{
return Game::scanner_info->GetConsoleList(p, last, newest_only);
@@ -236,7 +236,7 @@ void GameInstance::Died()
{
ScriptInstance::Died();

ShowAIDebugWindow(OWNER_DEITY);
ShowAIDebugWindowIfAIError(OWNER_DEITY);

const GameInfo *info = Game::GetInfo();
if (info != nullptr) {
@@ -127,7 +127,9 @@ static void _GenerateWorld()
/* Make the map the height of the setting */
if (_game_mode != GM_MENU) FlatEmptyWorld(_settings_game.game_creation.se_flat_world_height);

ConvertGroundTilesIntoWaterTiles();
if (!_settings_game.construction.freeform_edges || _settings_game.game_creation.se_flat_world_height == 0) {
ConvertGroundTilesIntoWaterTiles();
}
IncreaseGeneratingWorldProgress(GWP_OBJECT);
} else {
GenerateLandscape(_gw.mode);
@@ -124,7 +124,7 @@ static const NWidgetPart _nested_generate_landscape_widgets[] = {
/* Snow line. */
NWidget(NWID_HORIZONTAL),
NWidget(WWT_IMGBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(0, 1),
NWidget(WWT_TEXTBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_TEXT), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_IMGBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(0, 1),
EndContainer(),
/* Starting date. */
@@ -233,7 +233,7 @@ static const NWidgetPart _nested_heightmap_load_widgets[] = {
NWidget(WWT_TEXT, COLOUR_ORANGE, WID_GL_HEIGHTMAP_SIZE_TEXT), SetDataTip(STR_MAPGEN_HEIGHTMAP_SIZE, STR_NULL), SetFill(1, 0),
NWidget(NWID_HORIZONTAL),
NWidget(WWT_IMGBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_DOWN), SetDataTip(SPR_ARROW_DOWN, STR_MAPGEN_SNOW_LINE_DOWN), SetFill(0, 1),
NWidget(WWT_TEXTBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_TEXT), SetDataTip(STR_BLACK_INT, STR_NULL), SetFill(1, 0),
NWidget(WWT_TEXTBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_TEXT), SetDataTip(STR_JUST_STRING, STR_NULL), SetFill(1, 0),
NWidget(WWT_IMGBTN, COLOUR_ORANGE, WID_GL_SNOW_LEVEL_UP), SetDataTip(SPR_ARROW_UP, STR_MAPGEN_SNOW_LINE_UP), SetFill(0, 1),
EndContainer(),
NWidget(NWID_HORIZONTAL),
@@ -337,7 +337,15 @@ struct GenerateLandscapeWindow : public Window {
case WID_GL_MAPSIZE_X_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_x); break;
case WID_GL_MAPSIZE_Y_PULLDOWN: SetDParam(0, 1LL << _settings_newgame.game_creation.map_y); break;
case WID_GL_MAX_HEIGHTLEVEL_TEXT: SetDParam(0, _settings_newgame.construction.max_heightlevel); break;
case WID_GL_SNOW_LEVEL_TEXT: SetDParam(0, _settings_newgame.game_creation.snow_line_height); break;

case WID_GL_SNOW_LEVEL_TEXT:
if (_settings_newgame.game_creation.snow_line_height != MIN_SNOWLINE_HEIGHT - 1 && _settings_newgame.game_creation.snow_line_height != MAX_SNOWLINE_HEIGHT + 1) {
SetDParam(0, STR_BLACK_INT);
SetDParam(1, _settings_newgame.game_creation.snow_line_height);
} else {
SetDParam(0, STR_TIMETABLE_AUTOFILL);
}
break;

case WID_GL_TOWN_PULLDOWN:
if (_game_mode == GM_EDITOR) {
@@ -427,8 +435,8 @@ struct GenerateLandscapeWindow : public Window {
this->SetWidgetDisabledState(WID_GL_MAX_HEIGHTLEVEL_UP, _settings_newgame.construction.max_heightlevel >= MAX_MAX_HEIGHTLEVEL);
this->SetWidgetDisabledState(WID_GL_START_DATE_DOWN, _settings_newgame.game_creation.starting_year <= MIN_YEAR);
this->SetWidgetDisabledState(WID_GL_START_DATE_UP, _settings_newgame.game_creation.starting_year >= MAX_YEAR);
this->SetWidgetDisabledState(WID_GL_SNOW_LEVEL_DOWN, _settings_newgame.game_creation.snow_line_height <= MIN_SNOWLINE_HEIGHT || _settings_newgame.game_creation.landscape != LT_ARCTIC);
this->SetWidgetDisabledState(WID_GL_SNOW_LEVEL_UP, _settings_newgame.game_creation.snow_line_height >= MAX_SNOWLINE_HEIGHT || _settings_newgame.game_creation.landscape != LT_ARCTIC);
this->SetWidgetDisabledState(WID_GL_SNOW_LEVEL_DOWN, _settings_newgame.game_creation.snow_line_height <= MIN_SNOWLINE_HEIGHT - 1 || _settings_newgame.game_creation.landscape != LT_ARCTIC);
this->SetWidgetDisabledState(WID_GL_SNOW_LEVEL_UP, _settings_newgame.game_creation.snow_line_height >= MAX_SNOWLINE_HEIGHT + 1 || _settings_newgame.game_creation.landscape != LT_ARCTIC);

/* Do not allow a custom sea level with the original land generator. */
if (_settings_newgame.game_creation.land_generator == LG_ORIGINAL &&
@@ -622,7 +630,7 @@ struct GenerateLandscapeWindow : public Window {
if (!(this->flags & WF_TIMEOUT) || this->timeout_timer <= 1) {
this->HandleButtonClick(widget);

_settings_newgame.game_creation.snow_line_height = Clamp(_settings_newgame.game_creation.snow_line_height + widget - WID_GL_SNOW_LEVEL_TEXT, MIN_SNOWLINE_HEIGHT, MAX_SNOWLINE_HEIGHT);
_settings_newgame.game_creation.snow_line_height = Clamp(_settings_newgame.game_creation.snow_line_height + widget - WID_GL_SNOW_LEVEL_TEXT, MIN_SNOWLINE_HEIGHT - 1, MAX_SNOWLINE_HEIGHT + 1);
this->InvalidateData();
}
_left_button_clicked = false;
@@ -735,7 +743,7 @@ struct GenerateLandscapeWindow : public Window {
if ((uint)index == CUSTOM_TOWN_NUMBER_DIFFICULTY) {
this->widget_id = widget;
SetDParam(0, _settings_newgame.game_creation.custom_town_number);
ShowQueryString(STR_JUST_INT, STR_MAPGEN_NUMBER_OF_TOWNS, 5, this, CS_NUMERAL, QSF_NONE);
ShowQueryString(STR_JUST_INT, STR_MAPGEN_NUMBER_OF_TOWNS, 6, this, CS_NUMERAL, QSF_NONE);
}
_settings_newgame.difficulty.number_towns = index;
break;
@@ -789,7 +797,7 @@ struct GenerateLandscapeWindow : public Window {

case WID_GL_SNOW_LEVEL_TEXT:
this->SetWidgetDirty(WID_GL_SNOW_LEVEL_TEXT);
_settings_newgame.game_creation.snow_line_height = Clamp(value, MIN_SNOWLINE_HEIGHT, MAX_SNOWLINE_HEIGHT);
_settings_newgame.game_creation.snow_line_height = Clamp(value, MIN_SNOWLINE_HEIGHT - 1, MAX_SNOWLINE_HEIGHT + 1);
break;

case WID_GL_TOWN_PULLDOWN:
@@ -405,7 +405,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
max_icon_height = max(max_icon_height, GetSpriteSize(this->GetWidget<NWidgetCore>(WID_GL_REPLACE_PROTECTION)->widget_data).height);

/* ... minus the height of the group info ... */
max_icon_height += (FONT_HEIGHT_NORMAL * 3) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
max_icon_height += (FONT_HEIGHT_NORMAL * 4) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;

/* Get a multiple of tiny_step_height of that amount */
size->height = Ceil(size->height - max_icon_height, tiny_step_height);
@@ -428,6 +428,8 @@ class VehicleGroupWindow : public BaseVehicleListWindow {

case WID_GL_LIST_VEHICLE:
this->ComputeGroupInfoSize();
for (uint i = 0; i < 4; i++) SetDParamMaxValue(i, INT32_MAX);
size->width = max(size->width, GetStringBoundingBox(STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR_LIFETIME).width);
resize->height = GetVehicleListHeight(this->vli.vtype, this->tiny_step_height);
size->height = 4 * resize->height;
break;
@@ -441,7 +443,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
}

case WID_GL_INFO: {
size->height = (FONT_HEIGHT_NORMAL * 3) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
size->height = (FONT_HEIGHT_NORMAL * 4) + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM;
break;
}
}
@@ -572,6 +574,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
case WID_GL_INFO: {
Money this_year = 0;
Money last_year = 0;
Money lifetime = 0;
uint32 occupancy = 0;
size_t vehicle_count = this->vehicles.size();

@@ -581,6 +584,7 @@ class VehicleGroupWindow : public BaseVehicleListWindow {

this_year += v->GetDisplayProfitThisYear();
last_year += v->GetDisplayProfitLastYear();
lifetime += v->GetDisplayProfitLifetime();
occupancy += v->trip_occupancy;
}

@@ -597,6 +601,11 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
SetDParam(0, last_year);
DrawString(left, right, y, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);

y += FONT_HEIGHT_NORMAL;
DrawString(left, right, y, STR_GROUP_LIFETIME_PROFIT, TC_BLACK);
SetDParam(0, lifetime);
DrawString(left, right, y, STR_JUST_CURRENCY_LONG, TC_BLACK, SA_RIGHT);

y += FONT_HEIGHT_NORMAL;
DrawString(left, right, y, STR_GROUP_OCCUPANCY, TC_BLACK);
if (vehicle_count > 0) {
@@ -666,7 +675,8 @@ class VehicleGroupWindow : public BaseVehicleListWindow {
break;

case WID_GL_SORT_BY_DROPDOWN: // Select sorting criteria dropdown menu
ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_GL_SORT_BY_DROPDOWN, 0, (this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : (1 << 10));
ShowDropDownMenu(this, this->vehicle_sorter_names, this->vehicles.SortType(), WID_GL_SORT_BY_DROPDOWN, 0,
(this->vli.vtype == VEH_TRAIN || this->vli.vtype == VEH_ROAD) ? 0 : this->vehicle_sorter_non_ground_veh_disable_mask);
return;

case WID_GL_ALL_VEHICLES: // All vehicles button
@@ -498,9 +498,6 @@ void LoadHeightmap(DetailedFileType dft, const char *filename)

GrayscaleToMapHeights(x, y, map);
free(map);

FixSlopes();
MarkWholeScreenDirty();
}

/**
@@ -516,6 +513,6 @@ void FlatEmptyWorld(byte tile_height)
}
}

FixSlopes();
MarkWholeScreenDirty();
if (edge_distance != 0 && tile_height > 1) FixSlopes();
if (_settings_game.game_creation.landscape == LT_ARCTIC) DetermineSnowLineHeight(tile_height);
}
@@ -25,5 +25,6 @@ bool GetHeightmapDimensions(DetailedFileType dft, const char *filename, uint *x,
void LoadHeightmap(DetailedFileType dft, const char *filename);
void FlatEmptyWorld(byte tile_height);
void FixSlopes();
void DetermineSnowLineHeight(int flat_world_height = -1);

#endif /* HEIGHTMAP_H */
@@ -39,6 +39,7 @@
#include "object_base.h"
#include "game/game.hpp"
#include "error.h"
#include "company_base.h"

#include "table/strings.h"
#include "table/industry_land.h"
@@ -151,8 +152,9 @@ Industry::~Industry()
if (GetIndustryIndex(tile_cur) == this->index) {
DeleteNewGRFInspectWindow(GSF_INDUSTRYTILES, tile_cur);

Owner oc = GetWaterClass(tile_cur) == WATER_CLASS_CANAL ? GetCanalOwner(tile_cur) : INVALID_OWNER;
/* MakeWaterKeepingClass() can also handle 'land' */
MakeWaterKeepingClass(tile_cur, OWNER_NONE);
MakeWaterKeepingClass(tile_cur, oc);
}
} else if (IsTileType(tile_cur, MP_STATION) && IsOilRig(tile_cur)) {
DeleteOilRig(tile_cur);
@@ -488,6 +490,14 @@ static void GetTileDesc_Industry(TileIndex tile, TileDesc *td)
if (is->grf_prop.grffile != nullptr) {
td->grf = GetGRFConfig(is->grf_prop.grffile->grfid)->GetName();
}

if (HasTileWaterGround(tile) && GetWaterClass(tile) == WATER_CLASS_CANAL) {
Owner canal_owner = GetCanalOwner(tile);
if (canal_owner != td->owner[0]) {
td->owner_type[1] = STR_LAND_AREA_INFORMATION_CANAL_OWNER;
td->owner[1] = GetCanalOwner(tile);
}
}
}

static CommandCost ClearTile_Industry(TileIndex tile, DoCommandFlag flags)
@@ -946,6 +956,14 @@ static void ChangeTileOwner_Industry(TileIndex tile, Owner old_owner, Owner new_
/* If the founder merges, the industry was created by the merged company */
Industry *i = Industry::GetByTile(tile);
if (i->founder == old_owner) i->founder = (new_owner == INVALID_OWNER) ? OWNER_NONE : new_owner;

if (HasTileWaterGround(tile) && GetWaterClass(tile) == WATER_CLASS_CANAL) {
if (GetCanalOwner(tile) == old_owner) {
Company::Get(old_owner)->infrastructure.water--;
if (new_owner != INVALID_OWNER) Company::Get(new_owner)->infrastructure.water++;
SetCanalOwner(tile, new_owner == INVALID_OWNER ? OWNER_NONE : new_owner);
}
}
}

/**
@@ -1018,10 +1036,6 @@ static void SetupFarmFieldFence(TileIndex tile, int size, byte type, DiagDirecti

static void PlantFarmField(TileIndex tile, IndustryID industry)
{
if (_settings_game.game_creation.landscape == LT_ARCTIC) {
if (GetTileZ(tile) + 2 >= GetSnowLine()) return;
}

/* determine field size */
uint32 r = (Random() & 0x303) + 0x404;
if (_settings_game.game_creation.landscape == LT_ARCTIC) r += 0x404;
@@ -1295,7 +1309,7 @@ static CommandCost CheckNewIndustry_OilRig(TileIndex tile)
static CommandCost CheckNewIndustry_Farm(TileIndex tile)
{
if (_settings_game.game_creation.landscape == LT_ARCTIC) {
if (GetTileZ(tile) + 2 >= HighestSnowLine()) {
if (GetTileZ(tile) + 1 >= HighestSnowLine()) {
return_cmd_error(STR_ERROR_SITE_UNSUITABLE);
}
}
@@ -1449,6 +1463,7 @@ static CommandCost CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTil
}
} else {
CommandCost ret = EnsureNoVehicleOnGround(cur_tile);
if (ret.Succeeded()) ret = EnsureNoShipOnDiagDirs(cur_tile);
if (ret.Failed()) return ret;
if (IsBridgeAbove(cur_tile)) return_cmd_error(STR_ERROR_SITE_UNSUITABLE);

@@ -1481,10 +1496,21 @@ static CommandCost CheckIfIndustryTilesAreFree(TileIndex tile, const IndustryTil

if (ret.Failed()) return ret;
} else {
/* Clear the tiles, but do not affect town ratings */
CommandCost ret = DoCommand(cur_tile, 0, 0, DC_AUTO | DC_NO_TEST_TOWN_RATING | DC_NO_MODIFY_TOWN_RATING, CMD_LANDSCAPE_CLEAR);
if ((ind_behav & INDUSTRYBEH_BUILT_ONWATER) && IsWaterTile(cur_tile) && IsCanal(cur_tile) && !IsTileOwner(cur_tile, OWNER_NONE)) {
CommandCost ret = CheckTileOwnership(cur_tile);
if (ret.Failed()) {
if (!Company::IsValidID(_current_company)) {
if (_game_mode == GM_NORMAL) return ret;
} else {
if (!_settings_game.construction.build_on_competitor_canal) return ret;
}
}
} else {
/* Clear the tiles, but do not affect town ratings */
CommandCost ret = DoCommand(cur_tile, 0, 0, DC_AUTO | DC_NO_TEST_TOWN_RATING | DC_NO_MODIFY_TOWN_RATING, CMD_LANDSCAPE_CLEAR);

if (ret.Failed()) return ret;
if (ret.Failed()) return ret;
}
}
}
}
@@ -1880,10 +1906,14 @@ static void DoCreateNewIndustry(Industry *i, TileIndex tile, IndustryType type,
i->location.Add(cur_tile);

WaterClass wc = (IsWaterTile(cur_tile) ? GetWaterClass(cur_tile) : WATER_CLASS_INVALID);
Owner oc = wc == WATER_CLASS_CANAL ? GetCanalOwner(cur_tile) : INVALID_OWNER;
bool river = HasTileCanalOnRiver(cur_tile);

DoCommand(cur_tile, 0, 0, DC_EXEC | DC_NO_TEST_TOWN_RATING | DC_NO_MODIFY_TOWN_RATING, CMD_LANDSCAPE_CLEAR);

MakeIndustry(cur_tile, i->index, it.gfx, Random(), wc);
MakeIndustry(cur_tile, oc, i->index, it.gfx, Random(), wc);
if (Company::IsValidID(oc) && (founder == oc || founder >= MAX_COMPANIES)) Company::Get(oc)->infrastructure.water++;
if (river) SetCanalOnRiver(cur_tile);

if (_generating_world) {
SetIndustryConstructionCounter(cur_tile, 3);
@@ -2011,8 +2041,10 @@ CommandCost CmdBuildIndustry(TileIndex tile, DoCommandFlag flags, uint32 p1, uin
Industry *ind = nullptr;
if (deity_prospect || (_game_mode != GM_EDITOR && _current_company != OWNER_DEITY && _settings_game.construction.raw_industry_construction == 2 && indspec->IsRawIndustry())) {
if (flags & DC_EXEC) {
/* Prospected industries are build as OWNER_TOWN to not e.g. be build on owned land of the founder */
Backup<CompanyID> cur_company(_current_company, OWNER_TOWN, FILE_LINE);
/* Prospected industries not built on water are built as OWNER_TOWN to not e.g. be build on owned land of the founder */
Owner prospector = OWNER_TOWN;
if ((indspec->behaviour & INDUSTRYBEH_BUILT_ONWATER) && Company::IsValidID(_current_company)) prospector = _current_company;
Backup<CompanyID> cur_company(_current_company, prospector, FILE_LINE);
/* Prospecting has a chance to fail, however we cannot guarantee that something can
* be built on the map, so the chance gets lower when the map is fuller, but there
* is nothing we can really do about that. */
@@ -270,12 +270,13 @@ static inline void SetIndustryTriggers(TileIndex tile, byte triggers)
/**
* Make the given tile an industry tile
* @param t the tile to make an industry tile
* @param oc the owner of the canal (only set if it's placed on a canal).
* @param index the industry this tile belongs to
* @param gfx the graphics to use for the tile
* @param random the random value
* @param wc the water class for this industry; only useful when build on water
*/
static inline void MakeIndustry(TileIndex t, IndustryID index, IndustryGfx gfx, uint8 random, WaterClass wc)
static inline void MakeIndustry(TileIndex t, Owner oc, IndustryID index, IndustryGfx gfx, uint8 random, WaterClass wc)
{
SetTileType(t, MP_INDUSTRY);
_m[t].m1 = 0;
@@ -285,6 +286,7 @@ static inline void MakeIndustry(TileIndex t, IndustryID index, IndustryGfx gfx,
SetIndustryGfx(t, gfx); // m5, part of m6
SetIndustryTriggers(t, 0); // rest of m6
SetWaterClass(t, wc);
if (wc == WATER_CLASS_CANAL) SetCanalOwner(t, oc);
_me[t].m7 = 0;
}

@@ -630,8 +630,8 @@ void SetSnowLine(byte table[SNOW_LINE_MONTHS][SNOW_LINE_DAYS])

for (uint i = 0; i < SNOW_LINE_MONTHS; i++) {
for (uint j = 0; j < SNOW_LINE_DAYS; j++) {
_snow_line->highest_value = max(_snow_line->highest_value, table[i][j]);
_snow_line->lowest_value = min(_snow_line->lowest_value, table[i][j]);
_snow_line->highest_value = max(_snow_line->highest_value, min((byte)MAX_SNOWLINE_HEIGHT, table[i][j]));
_snow_line->lowest_value = min(_snow_line->lowest_value, max((byte)MIN_SNOWLINE_HEIGHT, table[i][j]));
}
}
}
@@ -643,11 +643,11 @@ void SetSnowLine(byte table[SNOW_LINE_MONTHS][SNOW_LINE_DAYS])
*/
byte GetSnowLine()
{
if (_snow_line == nullptr) return _settings_game.game_creation.snow_line_height;
if (_snow_line == nullptr) return Clamp(_settings_game.game_creation.snow_line_height, MIN_SNOWLINE_HEIGHT, MAX_SNOWLINE_HEIGHT);

YearMonthDay ymd;
ConvertDateToYMD(_date, &ymd);
return _snow_line->table[ymd.month][ymd.day];
return Clamp(_snow_line->table[ymd.month][ymd.day], MIN_SNOWLINE_HEIGHT, MAX_SNOWLINE_HEIGHT);
}

/**
@@ -657,7 +657,7 @@ byte GetSnowLine()
*/
byte HighestSnowLine()
{
return _snow_line == nullptr ? _settings_game.game_creation.snow_line_height : _snow_line->highest_value;
return min((byte)MAX_SNOWLINE_HEIGHT, _snow_line == nullptr ? _settings_game.game_creation.snow_line_height : _snow_line->highest_value);
}

/**
@@ -667,7 +667,7 @@ byte HighestSnowLine()
*/
byte LowestSnowLine()
{
return _snow_line == nullptr ? _settings_game.game_creation.snow_line_height : _snow_line->lowest_value;
return max((byte)MIN_SNOWLINE_HEIGHT, _snow_line == nullptr ? _settings_game.game_creation.snow_line_height : _snow_line->lowest_value);
}

/**
@@ -865,7 +865,8 @@ static void GenerateTerrain(int type, uint flag)
uint x = r & MapMaxX();
uint y = (r >> MapLogX()) & MapMaxY();

if (x < 2 || y < 2) return;
if (x < (uint)2 + (_settings_game.construction.freeform_edges ? 1 : 0)) return;
if (y < (uint)2 + (_settings_game.construction.freeform_edges ? 1 : 0)) return;

DiagDirection direction = (DiagDirection)GB(r, 22, 2);
uint w = templ->width;
@@ -882,26 +883,26 @@ static void GenerateTerrain(int type, uint flag)

switch (flag & 3) {
default: NOT_REACHED();
case 0:
case 0: // Northern side
if (xw + yw > MapSize() - bias) return;
break;

case 1:
case 1: // Eastern side
if (yw < xw + bias) return;
break;

case 2:
case 2: // Southern side
if (xw + yw < MapSize() + bias) return;
break;

case 3:
case 3: // Western side
if (xw < yw + bias) return;
break;
}
}

if (x + w >= MapMaxX() - 1) return;
if (y + h >= MapMaxY() - 1) return;
if (x + w >= MapMaxX()) return;
if (y + h >= MapMaxY()) return;

TileIndex tile = TileXY(x, y);

@@ -963,6 +964,55 @@ static void GenerateTerrain(int type, uint flag)
}
}

/**
* Automatically determine the value for snow line height and set it
* @param flat_world_height value >= 0: land height a flat world gets
* value == -1: not generating a flat world
*/
void DetermineSnowLineHeight(int flat_world_height)
{
/* Determine snow line height only when snow_line_height is lower or higher than the minimum or maximum values for the setting */
if (_settings_game.game_creation.snow_line_height != MIN_SNOWLINE_HEIGHT - 1 && _settings_game.game_creation.snow_line_height != MAX_SNOWLINE_HEIGHT + 1) return;

if (flat_world_height >= 0) { // generating a flat world
/* This doesn't require the extensive computations below */
int max_value = min(MAX_SNOWLINE_HEIGHT, max((int)MIN_SNOWLINE_HEIGHT, flat_world_height - 2));
uint half_max_value = max_value / 2 + max_value % 2;
_settings_game.game_creation.snow_line_height = max(MIN_SNOWLINE_HEIGHT, half_max_value);
}

if (flat_world_height == -1) { // generating landscape
int h0_tile_count = 0; // count tiles at sea level
int highest_height = 0;
for (uint y = 0; y < MapSizeY(); y++) {
for (uint x = 0; x < MapSizeX(); x++) {
int height = TileHeight(TileXY(x, y));
if (height == 0) h0_tile_count++;
if (height > highest_height) highest_height = height;
}
}

/* Determine the snow line height and make it so that it's at most 50% of the land mass */
int land_mass_size = MapSizeX() * MapSizeY() - h0_tile_count; // available tiles above sea
int tile_count = 0, snow_line_height = 1;
while (tile_count < land_mass_size / 2 && snow_line_height <= highest_height) {
for (uint y = 0; y < MapSizeY(); y++) {
for (uint x = 0; x < MapSizeX(); x++) {
if (TileHeight(TileXY(x, y)) == snow_line_height) tile_count++;
}
}
snow_line_height++;
}

/* Farms can only generate below 'snow_line_height - 1' and above sea level,
* which limits minimum snow_line_height to 'snow_line_height - 1 > 1', thus '3'.
* Forests can only generate at a minimum of 'snow_line_height + 2'
* which limits maximum snow_line_height to 'highest_height - 2'.
* @see CheckNewIndustry_Farm and CheckNewIndustry_Forest. */
_settings_game.game_creation.snow_line_height = Clamp(snow_line_height, 3, max(3, min(MAX_SNOWLINE_HEIGHT, highest_height - 2)));
}
}


#include "table/genland.h"

@@ -1034,11 +1084,18 @@ static bool FindSpring(TileIndex tile, void *user_data)

if (num < 4) return false;

/* Are we near the top of a hill? */
for (int dx = -16; dx <= 16; dx++) {
for (int dy = -16; dy <= 16; dy++) {
TileIndex t = TileAddWrap(tile, dx, dy);
if (t != INVALID_TILE && GetTileMaxZ(t) > referenceHeight + 2) return false;
if (t != INVALID_TILE) {
/* Are we near the top of a hill? */
if (GetTileMaxZ(t) > referenceHeight + 2) return false;

/* Are we too close to another river? */
if (dx >= -8 && dx <= 8 && dy >= -8 && dy <= 8) {
if (IsWaterTile(t) && IsRiver(t)) return false;
}
}
}
}

@@ -1071,6 +1128,70 @@ static bool MakeLake(TileIndex tile, void *user_data)
return false;
}

/**
* Check whether a river could (logically) flow into a lock.
* @param tile the middle tile of a lock.
* @return true iff the water can be flowing into a lock.
*/
static bool IsPossibleLockLocationRecursively(TileIndex tile)
{
if (!IsPossibleLockLocation(tile)) return false;

DiagDirection dir = GetInclinedSlopeDirection(GetTileSlope(tile));
TileIndexDiff delta_mid = TileOffsByDiagDir(dir);

DiagDirection dir_rot = ChangeDiagDir(dir, DIAGDIRDIFF_90RIGHT);
TileIndexDiff delta_side = TileOffsByDiagDir(dir_rot);

for (int m = -1; m <= 1; m += 2) {
TileIndex tile_offset = tile + m * delta_mid;
if (IsValidTile(tile_offset)) {
if (DistanceFromEdgeDir(tile_offset, dir) == 0 || DistanceFromEdgeDir(tile_offset, ReverseDiagDir(dir)) == 0) return false;

for (int s = -1; s <= 1; s += 2) {
tile_offset = tile + m * delta_mid + s * delta_side;
if (IsValidTile(tile_offset)) {
if (!IsTileFlat(tile_offset) && IsPossibleLockLocationOnDiagDir(tile_offset, dir_rot)) return false;

tile_offset = tile + m * delta_mid + 2 * s * delta_side;
if (IsValidTile(tile_offset)) {
if (!IsTileFlat(tile_offset) && IsPossibleLockLocationOnDiagDir(tile_offset, dir_rot)) return false;
}
}
}

tile_offset = tile + 2 * m * delta_mid;
if (IsValidTile(tile_offset)) {
if (!IsTileFlat(tile_offset)) return false;

for (int s = -1; s <= 1; s += 2) {
tile_offset = tile + 2 * m * delta_mid + s * delta_side;
if (IsValidTile(tile_offset)) {
if (!IsTileFlat(tile_offset)) {
if (IsTileFlat(tile + m * delta_mid + s * delta_side)) return false;
if (IsPossibleLockLocationOnDiagDir(tile_offset, dir_rot)) return false;
}
}
}

tile_offset = tile + 3 * m * delta_mid;
if (IsValidTile(tile_offset)) {
if (IsPossibleLockLocationOnDiagDir(tile_offset, dir)) return false;
}

for (int s = -1; s <= 1; s += 2) {
tile_offset = tile + 3 * m * delta_mid + s * delta_side;
if (IsValidTile(tile_offset)) {
if (IsPossibleLockLocationOnDiagDir(tile_offset, dir)) return false;
}
}
}
}
}

return true;
}

/**
* Check whether a river at begin could (logically) flow down to end.
* @param begin The origin of the flow.
@@ -1088,9 +1209,9 @@ static bool FlowsDown(TileIndex begin, TileIndex end)

return heightEnd <= heightBegin &&
/* Slope either is inclined or flat; rivers don't support other slopes. */
(slopeEnd == SLOPE_FLAT || IsInclinedSlope(slopeEnd)) &&
(slopeEnd == SLOPE_FLAT || (IsInclinedSlope(slopeEnd) && IsPossibleLockLocationRecursively(end))) &&
/* Slope continues, then it must be lower... or either end must be flat. */
((slopeEnd == slopeBegin && heightEnd < heightBegin) || slopeEnd == SLOPE_FLAT || slopeBegin == SLOPE_FLAT);
((slopeEnd == slopeBegin && heightEnd < heightBegin) || slopeEnd == SLOPE_FLAT || (slopeBegin == SLOPE_FLAT && GetTileMaxZ(end) == heightBegin));
}

/* AyStar callback for checking whether we reached our destination. */
@@ -1182,9 +1303,10 @@ static void BuildRiver(TileIndex begin, TileIndex end)
* Try to flow the river down from a given begin.
* @param spring The springing point of the river.
* @param begin The begin point we are looking from; somewhere down hill from the spring.
* @param flowdown_count The number of times the river has flowed down
* @return True iff a river could/has been built, otherwise false.
*/
static bool FlowRiver(TileIndex spring, TileIndex begin)
static bool FlowRiver(TileIndex spring, TileIndex begin, uint flowdown_count = 0)
{
#define SET_MARK(x) marks.insert(x)
#define IS_MARKED(x) (marks.find(x) != marks.end())
@@ -1207,7 +1329,7 @@ static bool FlowRiver(TileIndex spring, TileIndex begin)
queue.pop_front();

uint height2 = TileHeight(end);
if (IsTileFlat(end) && (height2 < height || (height2 == height && IsWaterTile(end)))) {
if (IsTileFlat(end) && ((height2 < height && ++flowdown_count) || (height2 == height && IsWaterTile(end)))) {
found = true;
break;
}
@@ -1224,8 +1346,8 @@ static bool FlowRiver(TileIndex spring, TileIndex begin)

if (found) {
/* Flow further down hill. */
found = FlowRiver(spring, end);
} else if (count > 32) {
found = FlowRiver(spring, end, flowdown_count);
} else if (count > 32 && flowdown_count > 1) {
/* Maybe we can make a lake. Find the Nth of the considered tiles. */
TileIndex lakeCenter = 0;
int i = RandomRange(count - 1) + 1;
@@ -1263,6 +1385,43 @@ static bool FlowRiver(TileIndex spring, TileIndex begin)
return found;
}

/* Create additional river tiles around possible lock locations to connect them. */
static void ConnectPossibleLocksWithRivers()
{
for (TileIndex tile = 0; tile != MapSize(); tile++) {
if (IsValidTile(tile) && IsTileType(tile, MP_WATER) && IsRiver(tile)) {
Slope slope = GetTileSlope(tile);
if (!IsInclinedSlope(slope)) continue;

DiagDirection dir = GetInclinedSlopeDirection(slope);
TileIndexDiff delta_side = TileOffsByDiagDir(ChangeDiagDir(dir, DIAGDIRDIFF_90RIGHT));
TileIndexDiff delta_mid = TileOffsByDiagDir(dir);
int mid_counts[] = { 2, 1, 1 };
int side_counts[] = { 0, 1, -1 };

for (int m = -1; m <= 1; m += 2) {
for (int i = 0; i < 3; i++) {
TileIndex tile_offset = tile + m * mid_counts[i] * delta_mid + side_counts[i] * delta_side;

TileIndex t = INVALID_TILE;
if (side_counts[i] != 0) {
if (IsValidTile(tile_offset) && IsWaterTile(tile_offset)) {
tile_offset = tile + m * mid_counts[i] * delta_mid + side_counts[i] * delta_side + m * delta_mid;
if (IsValidTile(tile_offset) && !IsWaterTile(tile_offset) && IsTileFlat(tile_offset)) t = tile_offset;
}
} else if (IsValidTile(tile_offset) && !IsWaterTile(tile_offset)) t = tile_offset;

if (t != INVALID_TILE && IsValidTile(t)) {
MakeRiver(t, Random());
/* Remove desert directly around the river tile. */
CircularTileSearch(&t, 5, RiverModifyDesertZone, NULL);
}
}
}
}
}
}

/**
* Actually (try to) create some rivers.
*/
@@ -1282,6 +1441,7 @@ static void CreateRivers()
if (FlowRiver(t, t)) break;
}
}
ConnectPossibleLocksWithRivers();

/* Run tile loop to update the ground density. */
for (uint i = 0; i != 256; i++) {
@@ -1294,18 +1454,32 @@ void GenerateLandscape(byte mode)
{
/** Number of steps of landscape generation */
enum GenLandscapeSteps {
GLS_HEIGHTMAP = 3, ///< Loading a heightmap
GLS_TERRAGENESIS = 5, ///< Terragenesis generator
GLS_HEIGHTMAP = 2, ///< Loading a heightmap
GLS_TERRAGENESIS = 4, ///< Terragenesis generator
GLS_ORIGINAL = 2, ///< Original generator
GLS_ARCTIC = 1, ///< Extra step needed for arctic landscape
GLS_TROPIC = 12, ///< Extra steps needed for tropic landscape
GLS_OTHER = 0, ///< Extra steps for other landscapes
};
uint steps = (_settings_game.game_creation.landscape == LT_TROPIC) ? GLS_TROPIC : GLS_OTHER;
uint steps;
switch (_settings_game.game_creation.landscape) {
case LT_TEMPERATE:
case LT_TOYLAND:
steps = GLS_OTHER;
break;

case LT_ARCTIC:
steps = GLS_ARCTIC;
break;

case LT_TROPIC:
steps = GLS_TROPIC;
break;
}

if (mode == GWM_HEIGHTMAP) {
SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_HEIGHTMAP);
LoadHeightmap(_file_to_saveload.detail_ftype, _file_to_saveload.name);
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
} else if (_settings_game.game_creation.land_generator == LG_TERRAGENESIS) {
SetGeneratingWorldProgress(GWP_LANDSCAPE, steps + GLS_TERRAGENESIS);
GenerateTerrainPerlin();
@@ -1364,13 +1538,19 @@ void GenerateLandscape(byte mode)
}
}

/* Do not call IncreaseGeneratingWorldProgress() before FixSlopes(),
* it allows screen redraw. Drawing of broken slopes crashes the game */
FixSlopes();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
if (mode == GWM_HEIGHTMAP || _settings_game.game_creation.land_generator == LG_ORIGINAL) {
/* Do not call IncreaseGeneratingWorldProgress() before FixSlopes(),
* it allows screen redraw. Drawing of broken slopes crashes the game */
FixSlopes();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
}
ConvertGroundTilesIntoWaterTiles();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);

if (_settings_game.game_creation.landscape == LT_ARCTIC) {
DetermineSnowLineHeight();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
}
if (_settings_game.game_creation.landscape == LT_TROPIC) CreateDesertOrRainForest();

CreateRivers();
@@ -3407,8 +3407,6 @@ STR_VEHICLE_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}Padvoert
STR_VEHICLE_LIST_SHIP_TOOLTIP :{BLACK}Skepe - klik op skip vir inligting
STR_VEHICLE_LIST_AIRCRAFT_TOOLTIP :{BLACK}Vliegtuie - klik op vliegtuig vir inligting

STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR :{TINY_FONT}{BLACK}Wins die jaar: {CURRENCY_LONG} (verlede jaar: {CURRENCY_LONG})

STR_VEHICLE_LIST_AVAILABLE_TRAINS :Beskikbare treine
STR_VEHICLE_LIST_AVAILABLE_ROAD_VEHICLES :Beskikbare Voertuie
STR_VEHICLE_LIST_AVAILABLE_SHIPS :Beskikbare Skepe
@@ -3786,7 +3784,6 @@ STR_VEHICLE_INFO_MAX_SPEED_TYPE_RANGE :{BLACK}Maks. sp
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}Gewig: {LTBLUE}{WEIGHT_SHORT} {BLACK}Krag: {LTBLUE}{POWER}{BLACK} Maks. spoed: {LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}Massa: {LTBLUE}{WEIGHT_SHORT} {BLACK}Krag: {LTBLUE}{POWER}{BLACK} Maks. spoed: {LTBLUE}{VELOCITY} {BLACK}Maks. T.E.: {LTBLUE}{FORCE}

STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}Wins hierdie jaar: {LTBLUE}{CURRENCY_LONG} (vorige jaar: {CURRENCY_LONG})
STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}Betroubaarheid: {LTBLUE}{COMMA}% {BLACK}Hoeveel keer onklaar sedert laaste diens: {LTBLUE}{COMMA}

STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}Gebou: {LTBLUE}{NUM}{BLACK} Waarde: {LTBLUE}{CURRENCY_LONG}
@@ -4112,7 +4109,6 @@ STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW}Rekenaa
STR_AI_CONFIG_CAPTION :{WHITE}AI/Spel Konfigurasie
STR_AI_CONFIG_GAMELIST_TOOLTIP :{BLACK}Spel Skrip wat in die volgende spel gelaai sal word
STR_AI_CONFIG_AILIST_TOOLTIP :{BLACK}Die AIs wat om die volgende spel gelaai sal word
STR_AI_CONFIG_HUMAN_PLAYER :Mens Speler
STR_AI_CONFIG_RANDOM_AI :Lukraak AI
STR_AI_CONFIG_NONE :(geen)

@@ -4162,7 +4158,6 @@ STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Speletjie Skrif
STR_AI_SETTINGS_CLOSE :{BLACK}Maak toe
STR_AI_SETTINGS_RESET :{BLACK}Herstel
STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING}
STR_AI_SETTINGS_START_DELAY :Aantal Dae om die AI te begin na die vorige een (omenby): {ORANGE}{STRING}


# Textfile window
@@ -2818,8 +2818,6 @@ STR_VEHICLE_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}العر
STR_VEHICLE_LIST_SHIP_TOOLTIP :{BLACK}السفن - اضغط على السفينة للتفاصيل
STR_VEHICLE_LIST_AIRCRAFT_TOOLTIP :{BLACK}الطائرات - اضغط على الطائرة للمعلومات

STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR :{TINY_FONT}{BLACK} دخل السنة الحالية : {CURRENCY_LONG} السنة السابقة : {CURRENCY_LONG}

STR_VEHICLE_LIST_AVAILABLE_TRAINS :القطارات المتاحة
STR_VEHICLE_LIST_AVAILABLE_ROAD_VEHICLES :العربات المتاحة
STR_VEHICLE_LIST_AVAILABLE_SHIPS :السفن المتاحة
@@ -3145,7 +3143,6 @@ STR_VEHICLE_INFO_MAX_SPEED :{BLACK} الس
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK} الوزن {LTBLUE}{WEIGHT_SHORT} {BLACK} الطاقة {LTBLUE}{POWER}{BLACK} السرعى القصوى {LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK} الوزن {LTBLUE}{WEIGHT_SHORT} {BLACK} القوة {LTBLUE}{POWER}{BLACK} السرعة القصوى {LTBLUE}{VELOCITY} {BLACK} قوة السحب القصوى {LTBLUE}{FORCE}

STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK} دخل هذة السنة {LTBLUE}{CURRENCY_LONG} السنة السابقة - {CURRENCY_LONG}
STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK} الاعتمادية {LTBLUE}{COMMA} % {BLACK} الاعطال منذ اخر صيانة {LTBLUE}{COMMA}

STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK} بني {LTBLUE}{NUM}{BLACK} القيمة {LTBLUE}{CURRENCY_LONG}
@@ -3456,7 +3453,6 @@ STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW} شاش

# AI configuration window
STR_AI_CONFIG_CAPTION :{WHITE}إعدادات الذكاء الصناعي
STR_AI_CONFIG_HUMAN_PLAYER :لاعب انساني
STR_AI_CONFIG_RANDOM_AI :ذكاء صناعي عشوائي

STR_AI_CONFIG_MOVE_UP :{BLACK}انقل للاعلى
@@ -3189,8 +3189,6 @@ STR_VEHICLE_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}Errepide
STR_VEHICLE_LIST_SHIP_TOOLTIP :{BLACK}Itsasontziak - klikatu itsasontzian xehetasunak ikusteko
STR_VEHICLE_LIST_AIRCRAFT_TOOLTIP :{BLACK}Hegazkinak - klikatu hegazkinean xehetasunak ikusteko

STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR :{TINY_FONT}{BLACK}Urte honetako irabaziak: {CURRENCY_LONG} (iaz: {CURRENCY_LONG})

STR_VEHICLE_LIST_AVAILABLE_TRAINS :Tren eskuragarriak
STR_VEHICLE_LIST_AVAILABLE_ROAD_VEHICLES :Errepide ibilgailu eskuragarriak
STR_VEHICLE_LIST_AVAILABLE_SHIPS :Itsasontzi eskuragarriak
@@ -3531,7 +3529,6 @@ STR_VEHICLE_INFO_MAX_SPEED :{BLACK}Gehienez
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}Pisua: {LTBLUE}{WEIGHT_SHORT} {BLACK}Potentzia: {LTBLUE}{POWER}{BLACK} Gehienezko abiadura: {LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}Pisua: {LTBLUE}{WEIGHT_SHORT} {BLACK}Potentzia: {LTBLUE}{POWER}{BLACK} Gehienezko abiadura: {LTBLUE}{VELOCITY} {BLACK}Gehienezko trakzio indarra: {LTBLUE}{FORCE}

STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}Irabaziak aurten: {LTBLUE}{CURRENCY_LONG} (iaz: {CURRENCY_LONG})
STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}Fidagarritasuna: {LTBLUE}{COMMA}% {BLACK}Matxurak azken mantenimendutik: {LTBLUE}{COMMA}

STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}Sortua: {LTBLUE}{NUM}{BLACK} Balioa: {LTBLUE}{CURRENCY_LONG}
@@ -3850,7 +3847,6 @@ STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW}IA/Joko
STR_AI_CONFIG_CAPTION :{WHITE}IA/Joko Script-aren Ezarpenak
STR_AI_CONFIG_GAMELIST_TOOLTIP :{BLACK}Hurrengo jokoan kargatuko den Joko Script-a
STR_AI_CONFIG_AILIST_TOOLTIP :{BLACK}Hurrengo jokoan kargatuko diren IA-k
STR_AI_CONFIG_HUMAN_PLAYER :Jokalaria
STR_AI_CONFIG_RANDOM_AI :Ausazko IA
STR_AI_CONFIG_NONE :(ezer ez)

@@ -3893,7 +3889,6 @@ STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Jokoaren Script
STR_AI_SETTINGS_CLOSE :{BLACK}Itxi
STR_AI_SETTINGS_RESET :{BLACK}Berrabiarazi
STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING}
STR_AI_SETTINGS_START_DELAY :IA hau abiarazteko pasa beharko diren egunak (gutxi gora behera): {ORANGE}{STRING}


# Textfile window
@@ -3639,8 +3639,6 @@ STR_VEHICLE_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}Аўта
STR_VEHICLE_LIST_SHIP_TOOLTIP :{BLACK}Караблi: клікніце для атрыманьня даведкі
STR_VEHICLE_LIST_AIRCRAFT_TOOLTIP :{BLACK}Самалёты: клікніце для атрыманьня даведкі

STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR :{TINY_FONT}{BLACK}Прыбытак сёлета: {CURRENCY_LONG} (летась: {CURRENCY_LONG})

STR_VEHICLE_LIST_AVAILABLE_TRAINS :Даступныя цягнiкi
STR_VEHICLE_LIST_AVAILABLE_ROAD_VEHICLES :Даступныя аўтамабiлi
STR_VEHICLE_LIST_AVAILABLE_SHIPS :Даступныя караблi
@@ -4019,7 +4017,6 @@ STR_VEHICLE_INFO_MAX_SPEED_TYPE_RANGE :{BLACK}Макс
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}Вага: {LTBLUE}{WEIGHT_SHORT} {BLACK}Магутнасьць: {LTBLUE}{POWER}{BLACK} Макс. хуткасьць: {LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}Вага: {LTBLUE}{WEIGHT_SHORT} {BLACK}Магутнасьць: {LTBLUE}{POWER}{BLACK} Макс. хуткасьць: {LTBLUE}{VELOCITY} {BLACK}Макс. ЦН: {LTBLUE}{FORCE}

STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}Прыбытак сёлета: {LTBLUE}{CURRENCY_LONG} (летась: {CURRENCY_LONG})
STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}Надзейнасьць: {LTBLUE}{COMMA}% {BLACK}Паломак з апошняга агляду: {LTBLUE}{COMMA}

STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}Пабудаваны ў: {LTBLUE}{NUM} г.{BLACK} Кошт: {LTBLUE}{CURRENCY_LONG}
@@ -4344,7 +4341,6 @@ STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW}Вак
STR_AI_CONFIG_CAPTION :{WHITE}Канфігурацыя ШІ / скрыпту
STR_AI_CONFIG_GAMELIST_TOOLTIP :{BLACK}Гульнёвы скрыпт, які будзе загружаны ў наступнай гульні
STR_AI_CONFIG_AILIST_TOOLTIP :{BLACK}Модулі ШІ, якія будуць загружаны ў наступнай гульні
STR_AI_CONFIG_HUMAN_PLAYER :Чалавек
STR_AI_CONFIG_RANDOM_AI :Выпадковы ШI
STR_AI_CONFIG_NONE :(няма)

@@ -4387,7 +4383,6 @@ STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Гульнёв
STR_AI_SETTINGS_CLOSE :{BLACK}Закрыць
STR_AI_SETTINGS_RESET :{BLACK}Ськід
STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING}
STR_AI_SETTINGS_START_DELAY :Колькасьць дзён да старту гэтага AI/ШI пасьля папярэдняга: {ORANGE}{STRING}


# Textfile window
@@ -3349,8 +3349,6 @@ STR_VEHICLE_LIST_ROAD_VEHICLE_TOOLTIP :{BLACK}Automóv
STR_VEHICLE_LIST_SHIP_TOOLTIP :{BLACK}Embarcações - clique numa embarcação para informações
STR_VEHICLE_LIST_AIRCRAFT_TOOLTIP :{BLACK}Aeronave - clique na aeronave para informações

STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR :{TINY_FONT}{BLACK}Lucro anual: {CURRENCY_LONG} (último ano: {CURRENCY_LONG})

STR_VEHICLE_LIST_AVAILABLE_TRAINS :Trens disponíveis
STR_VEHICLE_LIST_AVAILABLE_ROAD_VEHICLES :Automóveis disponíveis
STR_VEHICLE_LIST_AVAILABLE_SHIPS :Embarcações disponíveis
@@ -3709,7 +3707,6 @@ STR_VEHICLE_INFO_MAX_SPEED_TYPE_RANGE :{BLACK}Vel. Má
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED :{BLACK}Peso: {LTBLUE}{WEIGHT_SHORT} {BLACK}Força: {LTBLUE}{POWER}{BLACK} Velocidade Max: {LTBLUE}{VELOCITY}
STR_VEHICLE_INFO_WEIGHT_POWER_MAX_SPEED_MAX_TE :{BLACK}Peso: {LTBLUE}{WEIGHT_SHORT} {BLACK}Força: {LTBLUE}{POWER}{BLACK} Velocidade Max: {LTBLUE}{VELOCITY} {BLACK}Max. T.E.: {LTBLUE}{FORCE}

STR_VEHICLE_INFO_PROFIT_THIS_YEAR_LAST_YEAR :{BLACK}Lucros desse ano: {LTBLUE}{CURRENCY_LONG} (ano passado: {CURRENCY_LONG})
STR_VEHICLE_INFO_RELIABILITY_BREAKDOWNS :{BLACK}Confiabilidade: {LTBLUE}{COMMA}% {BLACK}Quebras desde a última manutenção: {LTBLUE}{COMMA}

STR_VEHICLE_INFO_BUILT_VALUE :{LTBLUE}{ENGINE} {BLACK}Construído: {LTBLUE}{NUM}{BLACK} Valor: {LTBLUE}{CURRENCY_LONG}
@@ -4034,7 +4031,6 @@ STR_ERROR_AI_DEBUG_SERVER_ONLY :{YELLOW}Depura
STR_AI_CONFIG_CAPTION :{WHITE}Configuração da I.A./Script do jogo
STR_AI_CONFIG_GAMELIST_TOOLTIP :{BLACK}O script do jogo que será carregado no próximo jogo
STR_AI_CONFIG_AILIST_TOOLTIP :{BLACK}IAs que serão carregadas no próximo jogo
STR_AI_CONFIG_HUMAN_PLAYER :Jogador humano
STR_AI_CONFIG_RANDOM_AI :IA aleatória
STR_AI_CONFIG_NONE :{G=m}(nenhum)

@@ -4077,7 +4073,6 @@ STR_AI_SETTINGS_CAPTION_GAMESCRIPT :Script do jogo
STR_AI_SETTINGS_CLOSE :{BLACK}Fechar
STR_AI_SETTINGS_RESET :{BLACK}Resetar
STR_AI_SETTINGS_SETTING :{STRING}: {ORANGE}{STRING}
STR_AI_SETTINGS_START_DELAY :Número de dias para começar esta IA após a última: {ORANGE}{STRING}


# Textfile window