/** * State controlling game loop. * The state must not be changed from anywhere but here. * That check is enforced in DoCommand. */ void StateGameLoop() { /* dont execute the state loop during pause */ if (_pause_mode != PM_UNPAUSED) { UpdateLandscapingLimits(); Game::GameLoop(); CallWindowTickEvent(); return; } if (HasModalProgress()) return; ClearStorageChanges(false); if (_game_mode == GM_EDITOR) { RunTileLoop(); CallVehicleTicks(); CallLandscapeTick(); ClearStorageChanges(true); UpdateLandscapingLimits(); CallWindowTickEvent(); NewsLoop(); } else { if (_debug_desync_level > 2 && _date_fract == 0 && (_date & 0x1F) == 0) { /* Save the desync savegame if needed. */ char name[MAX_PATH]; snprintf(name, lengthof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date); SaveOrLoad(name, SL_SAVE, AUTOSAVE_DIR, false); } CheckCaches(); /* All these actions has to be done from OWNER_NONE * for multiplayer compatibility */ Backup<CompanyByte> cur_company(_current_company, OWNER_NONE, FILE_LINE); AnimateAnimatedTiles(); IncreaseDate(); RunTileLoop(); CallVehicleTicks(); CallLandscapeTick(); ClearStorageChanges(true); AI::GameLoop(); Game::GameLoop(); UpdateLandscapingLimits(); CallWindowTickEvent(); NewsLoop(); cur_company.Restore(); } assert(IsLocalCompany()); }
/*! * Toplevel network safe docommand function for the current company. Must not be called recursively. * The callback is called when the command succeeded or failed. The parameters * tile, p1 and p2 are from the #CommandProc function. The paramater cmd is the command to execute. * The parameter my_cmd is used to indicate if the command is from a company or the server. * * @param tile The tile to perform a command on (see #CommandProc) * @param p1 Additional data for the command (see #CommandProc) * @param p2 Additional data for the command (see #CommandProc) * @param cmd The command to execute (a CMD_* value) * @param callback A callback function to call after the command is finished * @param text The text to pass * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) * @return true if the command succeeded, else false */ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd) { assert(_docommand_recursive == 0); CommandCost res, res2; int x = TileX(tile) * TILE_SIZE; int y = TileY(tile) * TILE_SIZE; _error_message = INVALID_STRING_ID; StringID error_part1 = GB(cmd, 16, 16); _additional_cash_required = 0; /* get pointer to command handler */ byte cmd_id = cmd & CMD_ID_MASK; assert(cmd_id < lengthof(_command_proc_table)); CommandProc *proc = _command_proc_table[cmd_id].proc; if (proc == NULL) return false; /* Command flags are used internally */ uint cmd_flags = GetCommandFlags(cmd); /* Flags get send to the DoCommand */ DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return false; /* Always execute server and spectator commands as spectator */ if (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) _current_company = COMPANY_SPECTATOR; CompanyID old_company = _current_company; /* If the company isn't valid it may only do server command or start a new company! * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) == 0 && !Company::IsValidID(_current_company)) { if (my_cmd) ShowErrorMessage(error_part1, _error_message, x, y); return false; } bool notest = (cmd_flags & CMD_NO_TEST) != 0; _docommand_recursive = 1; /* cost estimation only? */ if (!IsGeneratingWorld() && _shift_pressed && IsLocalCompany() && !(cmd & CMD_NETWORK_COMMAND) && cmd_id != CMD_PAUSE) { /* estimate the cost. */ SetTownRatingTestMode(true); res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); ShowErrorMessage(error_part1, _error_message, x, y); } else { ShowEstimatedCostOrIncome(res.GetCost(), x, y); } _docommand_recursive = 0; ClearStorageChanges(false); return false; } if (!((cmd & CMD_NO_TEST_IF_IN_NETWORK) && _networking)) { /* first test if the command can be executed. */ SetTownRatingTestMode(true); res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); assert(cmd_id == CMD_COMPANY_CTRL || old_company == _current_company); if (CmdFailed(res)) { res.SetGlobalErrorMessage(); goto show_error; } /* no money? Only check if notest is off */ if (!notest && res.GetCost() != 0 && !CheckCompanyHasMoney(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 (_networking && !(cmd & CMD_NETWORK_COMMAND)) { NetworkSend_Command(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text); _docommand_recursive = 0; ClearStorageChanges(false); return true; } #endif /* ENABLE_NETWORK */ DEBUG(desync, 1, "cmd: %08x; %08x; %1x; %06x; %08x; %08x; %04x; %s\n", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text); /* update last build coordinate of company. */ if (tile != 0) { Company *c = Company::GetIfValid(_current_company); if (c != NULL) c->last_build_coordinate = tile; } /* Actually try and execute the command. If no cost-type is given * use the construction one */ res2 = proc(tile, flags | DC_EXEC, p1, p2, text); assert(cmd_id == CMD_COMPANY_CTRL || old_company == _current_company); /* 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.GetCost() == res2.GetCost() && CmdFailed(res) == CmdFailed(res2)); // sanity check } else { if (CmdFailed(res2)) { res2.SetGlobalErrorMessage(); goto show_error; } } SubtractMoneyFromCompany(res2); /* update signals if needed */ UpdateSignalsInBuffer(); if (IsLocalCompany() && _game_mode != GM_EDITOR) { if (res2.GetCost() != 0 && tile != 0) ShowCostOrIncomeAnimation(x, y, GetSlopeZ(x, y), res2.GetCost()); if (_additional_cash_required != 0) { SetDParam(0, _additional_cash_required); if (my_cmd) ShowErrorMessage(error_part1, STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY, x, y); if (res2.GetCost() == 0) goto callb_err; } } _docommand_recursive = 0; if (callback) callback(true, tile, p1, p2); ClearStorageChanges(true); return true; show_error: /* show error message if the command fails? */ if (IsLocalCompany() && error_part1 != 0 && my_cmd) { ShowErrorMessage(error_part1, _error_message, x, y); } callb_err: _docommand_recursive = 0; if (callback) callback(false, tile, p1, p2); ClearStorageChanges(false); return false; }
/*! * Helper function for the toplevel network safe docommand function for the current company. * * @param tile The tile to perform a command on (see #CommandProc) * @param p1 Additional data for the command (see #CommandProc) * @param p2 Additional data for the command (see #CommandProc) * @param cmd The command to execute (a CMD_* value) * @param callback A callback function to call after the command is finished * @param text The text to pass * @param my_cmd indicator if the command is from a company or server (to display error messages for a user) * @param estimate_only whether to give only the estimate or also execute the command * @return the command cost of this function. */ CommandCost DoCommandPInternal(TileIndex tile, uint32 p1, uint32 p2, uint32 cmd, CommandCallback *callback, const char *text, bool my_cmd, bool estimate_only) { /* Prevent recursion; it gives a mess over the network */ assert(_docommand_recursive == 0); _docommand_recursive = 1; /* Reset the state. */ _additional_cash_required = 0; /* Get pointer to command handler */ byte cmd_id = cmd & CMD_ID_MASK; assert(cmd_id < lengthof(_command_proc_table)); CommandProc *proc = _command_proc_table[cmd_id].proc; /* Shouldn't happen, but you never know when someone adds * NULLs to the _command_proc_table. */ assert(proc != NULL); /* Command flags are used internally */ CommandFlags cmd_flags = GetCommandFlags(cmd); /* Flags get send to the DoCommand */ DoCommandFlag flags = CommandFlagsToDCFlags(cmd_flags); #ifdef ENABLE_NETWORK /* Make sure p2 is properly set to a ClientID. */ assert(!(cmd_flags & CMD_CLIENT_ID) || p2 != 0); #endif /* Do not even think about executing out-of-bounds tile-commands */ if (tile != 0 && (tile >= MapSize() || (!IsValidTile(tile) && (cmd_flags & CMD_ALL_TILES) == 0))) return_dcpi(CMD_ERROR, false); /* Always execute server and spectator commands as spectator */ bool exec_as_spectator = (cmd_flags & (CMD_SPECTATOR | CMD_SERVER)) != 0; /* If the company isn't valid it may only do server command or start a new company! * The server will ditch any server commands a client sends to it, so effectively * this guards the server from executing functions for an invalid company. */ if (_game_mode == GM_NORMAL && !exec_as_spectator && !Company::IsValidID(_current_company) && !(_current_company == OWNER_DEITY && (cmd_flags & CMD_DEITY) != 0)) { return_dcpi(CMD_ERROR, false); } Backup<CompanyByte> cur_company(_current_company, FILE_LINE); if (exec_as_spectator) cur_company.Change(COMPANY_SPECTATOR); bool test_and_exec_can_differ = (cmd_flags & CMD_NO_TEST) != 0; /* Test the command. */ _cleared_object_areas.Clear(); SetTownRatingTestMode(true); ClearStorageChanges(false); CommandCost res = proc(tile, flags, p1, p2, text); SetTownRatingTestMode(false); /* Make sure we're not messing things up here. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); /* If the command fails, we're doing an estimate * or the player does not have enough money * (unless it's a command where the test and * execution phase might return different costs) * we bail out here. */ if (res.Failed() || estimate_only || (!test_and_exec_can_differ && !CheckCompanyHasMoney(res))) { if (!_networking || _generating_world || (cmd & CMD_NETWORK_COMMAND) != 0) { /* Log the failed command as well. Just to be able to be find * causes of desyncs due to bad command test implementations. */ DEBUG(desync, 1, "cmdf: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); } cur_company.Restore(); return_dcpi(res, false); } #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 (_networking && !_generating_world && !(cmd & CMD_NETWORK_COMMAND)) { NetworkSendCommand(tile, p1, p2, cmd & ~CMD_FLAGS_MASK, callback, text, _current_company); cur_company.Restore(); /* Don't return anything special here; no error, no costs. * This way it's not handled by DoCommand and only the * actual execution of the command causes messages. Also * reset the storages as we've not executed the command. */ return_dcpi(CommandCost(), false); } #endif /* ENABLE_NETWORK */ DEBUG(desync, 1, "cmd: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, tile, p1, p2, cmd & ~CMD_NETWORK_COMMAND, text, GetCommandName(cmd)); /* Actually try and execute the command. If no cost-type is given * use the construction one */ _cleared_object_areas.Clear(); ClearStorageChanges(false); CommandCost res2 = proc(tile, flags | DC_EXEC, p1, p2, text); if (cmd_id == CMD_COMPANY_CTRL) { cur_company.Trash(); /* We are a new company -> Switch to new local company. * We were closed down -> Switch to spectator * Some other company opened/closed down -> The outside function will switch back */ _current_company = _local_company; } else { /* Make sure nothing bad happened, like changing the current company. */ assert(exec_as_spectator ? _current_company == COMPANY_SPECTATOR : cur_company.Verify()); cur_company.Restore(); } /* If the test and execution can differ we have to check the * return of the command. Otherwise we can check whether the * test and execution have yielded the same result, * i.e. cost and error state are the same. */ if (!test_and_exec_can_differ) { assert(res.GetCost() == res2.GetCost() && res.Failed() == res2.Failed()); // sanity check } else if (res2.Failed()) { return_dcpi(res2, false); } /* If we're needing more money and we haven't done * anything yet, ask for the money! */ if (_additional_cash_required != 0 && res2.GetCost() == 0) { /* It could happen we removed rail, thus gained money, and deleted something else. * So make sure the signal buffer is empty even in this case */ UpdateSignalsInBuffer(); SetDParam(0, _additional_cash_required); return_dcpi(CommandCost(STR_ERROR_NOT_ENOUGH_CASH_REQUIRES_CURRENCY), false); } /* update last build coordinate of company. */ if (tile != 0) { Company *c = Company::GetIfValid(_current_company); if (c != NULL) c->last_build_coordinate = tile; } SubtractMoneyFromCompany(res2); /* update signals if needed */ UpdateSignalsInBuffer(); return_dcpi(res2, true); }
/** * The internal, real, generate function. */ static void _GenerateWorld(void *) { /* Make sure everything is done via OWNER_NONE. */ Backup<CompanyByte> _cur_company(_current_company, OWNER_NONE, FILE_LINE); try { _generating_world = true; _genworld_mapgen_mutex->BeginCritical(); if (_network_dedicated) DEBUG(net, 0, "Generating map, please wait..."); /* Set the Random() seed to generation_seed so we produce the same map with the same seed */ if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom(); _random.SetSeed(_settings_game.game_creation.generation_seed); SetGeneratingWorldProgress(GWP_MAP_INIT, 2); SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); IncreaseGeneratingWorldProgress(GWP_MAP_INIT); /* Must start economy early because of the costs. */ StartupEconomy(); /* Don't generate landscape items when in the scenario editor. */ if (_gw.mode == GWM_EMPTY) { SetGeneratingWorldProgress(GWP_OBJECT, 1); /* Make sure the tiles at the north border are void tiles if needed. */ if (_settings_game.construction.freeform_edges) { for (uint row = 0; row < MapSizeY(); row++) MakeVoid(TileXY(0, row)); for (uint col = 0; col < MapSizeX(); col++) MakeVoid(TileXY(col, 0)); } /* Make the map the height of the setting */ if (_game_mode != GM_MENU) FlatEmptyWorld(_settings_game.game_creation.se_flat_world_height); ConvertGroundTilesIntoWaterTiles(); IncreaseGeneratingWorldProgress(GWP_OBJECT); } else { GenerateLandscape(_gw.mode); GenerateClearTile(); /* only generate towns, tree and industries in newgame mode. */ if (_game_mode != GM_EDITOR) { if (!GenerateTowns(_settings_game.economy.town_layout)) { _cur_company.Restore(); HandleGeneratingWorldAbortion(); return; } GenerateIndustries(); GenerateObjects(); GenerateTrees(); } } ClearStorageChanges(true); /* These are probably pointless when inside the scenario editor. */ SetGeneratingWorldProgress(GWP_GAME_INIT, 3); StartupCompanies(); IncreaseGeneratingWorldProgress(GWP_GAME_INIT); StartupEngines(); IncreaseGeneratingWorldProgress(GWP_GAME_INIT); StartupDisasters(); _generating_world = false; /* No need to run the tile loop in the scenario editor. */ if (_gw.mode != GWM_EMPTY) { uint i; SetGeneratingWorldProgress(GWP_RUNTILELOOP, 0x500); for (i = 0; i < 0x500; i++) { RunTileLoop(); IncreaseGeneratingWorldProgress(GWP_RUNTILELOOP); } } ResetObjectToPlace(); _cur_company.Trash(); _current_company = _local_company = _gw.lc; SetGeneratingWorldProgress(GWP_GAME_START, 1); /* Call any callback */ if (_gw.proc != NULL) _gw.proc(); IncreaseGeneratingWorldProgress(GWP_GAME_START); CleanupGeneration(); _genworld_mapgen_mutex->EndCritical(); ShowNewGRFError(); if (_network_dedicated) DEBUG(net, 0, "Map generated, starting game"); DEBUG(desync, 1, "new_map: %08x", _settings_game.game_creation.generation_seed); if (_debug_desync_level > 0) { char name[MAX_PATH]; snprintf(name, lengthof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date); SaveOrLoad(name, SL_SAVE, AUTOSAVE_DIR, false); } } catch (...) { if (_cur_company.IsValid()) _cur_company.Restore(); _generating_world = false; _genworld_mapgen_mutex->EndCritical(); throw; } }