static void DisasterClearSquare(TileIndex tile) { if (EnsureNoVehicleOnGround(tile).Failed()) return; switch (GetTileType(tile)) { case MP_RAILWAY: if (Company::IsHumanID(GetTileOwner(tile))) { Backup<CompanyByte> cur_company(_current_company, OWNER_WATER, FILE_LINE); DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); cur_company.Restore(); /* update signals in buffer */ UpdateSignalsInBuffer(); } break; case MP_HOUSE: { Backup<CompanyByte> cur_company(_current_company, OWNER_NONE, FILE_LINE); DoCommand(tile, 0, 0, DC_EXEC, CMD_LANDSCAPE_CLEAR); cur_company.Restore(); break; } case MP_TREES: case MP_CLEAR: DoClearSquare(tile); break; default: break; } }
/** * State controlling game loop. * The state must not be changed from anywhere but here. * That check is enforced in DoCommand. */ void StateGameLoop() { /* don't execute the state loop during pause */ if (_pause_mode != PM_UNPAUSED) { UpdateLandscapingLimits(); #ifndef DEBUG_DUMP_COMMANDS Game::GameLoop(); #endif CallWindowTickEvent(); return; } if (HasModalProgress()) return; Layouter::ReduceLineCache(); if (_game_mode == GM_EDITOR) { BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); RunTileLoop(); CallVehicleTicks(); CallLandscapeTick(); BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); 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]; seprintf(name, lastof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date); SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, 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); BasePersistentStorageArray::SwitchMode(PSM_ENTER_GAMELOOP); AnimateAnimatedTiles(); IncreaseDate(); RunTileLoop(); CallVehicleTicks(); CallLandscapeTick(); BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP); #ifndef DEBUG_DUMP_COMMANDS AI::GameLoop(); Game::GameLoop(); #endif UpdateLandscapingLimits(); CallWindowTickEvent(); NewsLoop(); cur_company.Restore(); } assert(IsLocalCompany()); }
/* static */ void AI::StartNew(CompanyID company, bool rerandomise_ai) { assert(Company::IsValidID(company)); /* Clients shouldn't start AIs */ if (_networking && !_network_server) return; AIConfig *config = AIConfig::GetConfig(company, AIConfig::SSS_FORCE_GAME); AIInfo *info = config->GetInfo(); if (info == NULL || (rerandomise_ai && config->IsRandom())) { info = AI::scanner_info->SelectRandomAI(); assert(info != NULL); /* Load default data and store the name in the settings */ config->Change(info->GetName(), -1, false, true); } Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company *c = Company::Get(company); c->ai_info = info; assert(c->ai_instance == NULL); c->ai_instance = new AIInstance(); c->ai_instance->Initialize(info); cur_company.Restore(); InvalidateWindowData(WC_AI_DEBUG, 0, -1); return; }
/* static */ void AI::GameLoop() { /* If we are in networking, only servers run this function, and that only if it is allowed */ if (_networking && (!_network_server || !_settings_game.ai.ai_in_multiplayer)) return; /* The speed with which AIs go, is limited by the 'competitor_speed' */ AI::frame_counter++; assert(_settings_game.difficulty.competitor_speed <= 4); if ((AI::frame_counter & ((1 << (4 - _settings_game.difficulty.competitor_speed)) - 1)) != 0) return; Backup<CompanyByte> cur_company(_current_company, FILE_LINE); const Company *c; FOR_ALL_COMPANIES(c) { if (c->is_ai) { cur_company.Change(c->index); c->ai_instance->GameLoop(); } } cur_company.Restore(); /* Occasionally collect garbage; every 255 ticks do one company. * Effectively collecting garbage once every two months per AI. */ if ((AI::frame_counter & 255) == 0) { CompanyID cid = (CompanyID)GB(AI::frame_counter, 8, 4); if (Company::IsValidAiID(cid)) Company::Get(cid)->ai_instance->CollectGarbage(); } }
/* static */ void Game::GameLoop() { if (_networking && !_network_server) { PerformanceMeasurer::SetInactive(PFE_GAMESCRIPT); return; } if (Game::instance == NULL) { PerformanceMeasurer::SetInactive(PFE_GAMESCRIPT); return; } PerformanceMeasurer framerate(PFE_GAMESCRIPT); Game::frame_counter++; Backup<CompanyByte> cur_company(_current_company, FILE_LINE); cur_company.Change(OWNER_DEITY); Game::instance->GameLoop(); cur_company.Restore(); /* Occasionally collect garbage */ if ((Game::frame_counter & 255) == 0) { Game::instance->CollectGarbage(); } }
/* static */ void AI::Unpause(CompanyID company) { Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company::Get(company)->ai_instance->Unpause(); cur_company.Restore(); }
/* static */ void Game::Uninitialize(bool keepConfig) { Backup<CompanyByte> cur_company(_current_company, FILE_LINE); delete Game::instance; Game::instance = NULL; Game::info = NULL; cur_company.Restore(); if (keepConfig) { Rescan(); } else { delete Game::scanner_info; delete Game::scanner_library; Game::scanner_info = NULL; Game::scanner_library = NULL; if (_settings_game.game_config != NULL) { delete _settings_game.game_config; _settings_game.game_config = NULL; } if (_settings_newgame.game_config != NULL) { delete _settings_newgame.game_config; _settings_newgame.game_config = NULL; } } }
/* static */ void AI::Suspend(CompanyID company) { if (_networking && !_network_server) return; Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company::Get(company)->ai_instance->Suspend(); cur_company.Restore(); }
/* static */ bool AI::IsPaused(CompanyID company) { Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); bool paused = Company::Get(company)->ai_instance->IsPaused(); cur_company.Restore(); return paused; }
/* static */ void Game::Save() { if (Game::instance != NULL && (!_networking || _network_server)) { Backup<CompanyByte> cur_company(_current_company, OWNER_DEITY, FILE_LINE); Game::instance->Save(); cur_company.Restore(); } else { GameInstance::SaveEmpty(); } }
/* static */ void Game::Load(int version) { if (Game::instance != NULL && (!_networking || _network_server)) { Backup<CompanyByte> cur_company(_current_company, OWNER_DEITY, FILE_LINE); Game::instance->Load(version); cur_company.Restore(); } else { /* Read, but ignore, the load data */ GameInstance::LoadEmpty(); } }
/** * 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()); }
/* static */ void AI::Pause(CompanyID company) { /* The reason why dedicated servers are forbidden to execute this * command is not because it is unsafe, but because there is no way * for the server owner to unpause the script again. */ if (_network_dedicated) return; Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company::Get(company)->ai_instance->Pause(); cur_company.Restore(); }
/* static */ void AI::Save(CompanyID company) { if (!_networking || _network_server) { Company *c = Company::GetIfValid(company); assert(c != NULL && c->ai_instance != NULL); Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); c->ai_instance->Save(); cur_company.Restore(); } else { AIInstance::SaveEmpty(); } }
/* static */ void AI::Load(CompanyID company, int version) { if (!_networking || _network_server) { Company *c = Company::GetIfValid(company); assert(c != NULL && c->ai_instance != NULL); Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); c->ai_instance->Load(version); cur_company.Restore(); } else { /* Read, but ignore, the load data */ AIInstance::LoadEmpty(); } }
/* static */ void AI::Stop(CompanyID company) { if (_networking && !_network_server) return; Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company *c = Company::Get(company); delete c->ai_instance; c->ai_instance = NULL; cur_company.Restore(); InvalidateWindowData(WC_AI_DEBUG, 0, -1); DeleteWindowById(WC_AI_SETTINGS, company); }
/* static */ void Game::GameLoop() { if (_networking && !_network_server) return; if (Game::instance == NULL) return; Game::frame_counter++; Backup<CompanyByte> cur_company(_current_company, FILE_LINE); cur_company.Change(OWNER_DEITY); Game::instance->GameLoop(); cur_company.Restore(); /* Occasionally collect garbage */ if ((Game::frame_counter & 255) == 0) { Game::instance->CollectGarbage(); } }
/** * Some data on this window has become invalid. * @param data Information about the changed data. * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details. */ virtual void OnInvalidateData(int data = 0, bool gui_scope = true) { if (data == -1 || ai_debug_company == data) this->SetDirty(); if (gui_scope && data == -2) { /* The continue button should be disabled when the game is unpaused and * it was previously paused by the break string ( = a line in the log * was highlighted )*/ if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED && this->highlight_row != -1) { this->DisableWidget(AID_WIDGET_CONTINUE_BTN); this->SetWidgetDirty(AID_WIDGET_CONTINUE_BTN); this->SetWidgetDirty(AID_WIDGET_LOG_PANEL); this->highlight_row = -1; } } /* If the log message is related to the active company tab, check the break string. * This needs to be done in gameloop-scope, so the AI is suspended immediately. */ if (!gui_scope && data == ai_debug_company && this->break_check_enabled && !StrEmpty(this->edit_str_buf)) { /* Get the log instance of the active company */ Backup<CompanyByte> cur_company(_current_company, ai_debug_company, FILE_LINE); AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer(); if (log != NULL && case_sensitive_break_check? strstr(log->lines[log->pos], this->edit_str_buf) != 0 : strcasestr(log->lines[log->pos], this->edit_str_buf) != 0) { AI::Suspend(ai_debug_company); if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) { DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE); } /* Make it possible to click on the continue button */ this->EnableWidget(AID_WIDGET_CONTINUE_BTN); this->SetWidgetDirty(AID_WIDGET_CONTINUE_BTN); /* Highlight row that matched */ this->highlight_row = log->pos; } cur_company.Restore(); } }
/** * Change the bank bank balance of a company by inserting or removing money without affecting the loan. * @param tile unused * @param flags operation to perform * @param p1 the amount of money to receive (if positive), or spend (if negative) * @param p2 (bit 0-7) - the company ID. * (bit 8-15) - the expenses type which should register the cost/income @see ExpensesType. * @param text unused * @return zero cost or an error */ CommandCost CmdChangeBankBalance(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { int32 delta = (int32)p1; CompanyID company = (CompanyID) GB(p2, 0, 8); ExpensesType expenses_type = Extract<ExpensesType, 8, 8>(p2); if (!Company::IsValidID(company)) return CMD_ERROR; if (expenses_type >= EXPENSES_END) return CMD_ERROR; if (_current_company != OWNER_DEITY) return CMD_ERROR; if (flags & DC_EXEC) { /* Change company bank balance of company. */ Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); SubtractMoneyFromCompany(CommandCost(expenses_type, -delta)); cur_company.Restore(); } /* This command doesn't cost anyting for deity. */ CommandCost zero_cost(expenses_type, 0); return zero_cost; }
/* static */ void Game::StartNew() { if (Game::instance != NULL) return; /* Clients shouldn't start GameScripts */ if (_networking && !_network_server) return; GameConfig *config = GameConfig::GetConfig(GameConfig::SSS_FORCE_GAME); GameInfo *info = config->GetInfo(); if (info == NULL) return; Backup<CompanyByte> cur_company(_current_company, FILE_LINE); cur_company.Change(OWNER_DEITY); Game::info = info; Game::instance = new GameInstance(); Game::instance->Initialize(info); cur_company.Restore(); InvalidateWindowData(WC_AI_DEBUG, 0, -1); }
/** Transfer funds (money) from one company to another. * To prevent abuse in multiplayer games you can only send money to other * companies if you have paid off your loan (either explicitely, or implicitely * given the fact that you have more money than loan). * @param tile unused * @param flags operation to perform * @param p1 the amount of money to transfer; max 20.000.000 * @param p2 the company to transfer the money to * @param text unused * @return the cost of this operation or an error */ CommandCost CmdGiveMoney(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { if (!_settings_game.economy.give_money) return CMD_ERROR; const Company *c = Company::Get(_current_company); CommandCost amount(EXPENSES_OTHER, min((Money)p1, (Money)20000000LL)); CompanyID dest_company = (CompanyID)p2; /* You can only transfer funds that is in excess of your loan */ if (c->money - c->current_loan < amount.GetCost() || amount.GetCost() < 0) return CMD_ERROR; if (!_networking || !Company::IsValidID(dest_company)) return CMD_ERROR; if (flags & DC_EXEC) { /* Add money to company */ Backup<CompanyByte> cur_company(_current_company, dest_company, FILE_LINE); SubtractMoneyFromCompany(CommandCost(EXPENSES_OTHER, -amount.GetCost())); cur_company.Restore(); } /* Subtract money from local-company */ return amount; }
/* static */ void Game::NewEvent(ScriptEvent *event) { /* AddRef() and Release() need to be called at least once, so do it here */ event->AddRef(); /* Clients should ignore events */ if (_networking && !_network_server) { event->Release(); return; } /* Check if Game instance is alive */ if (Game::instance == NULL) { event->Release(); return; } /* Queue the event */ Backup<CompanyByte> cur_company(_current_company, OWNER_DEITY, FILE_LINE); Game::instance->InsertEvent(event); cur_company.Restore(); event->Release(); }
/* static */ void AI::NewEvent(CompanyID company, ScriptEvent *event) { /* AddRef() and Release() need to be called at least once, so do it here */ event->AddRef(); /* Clients should ignore events */ if (_networking && !_network_server) { event->Release(); return; } /* Only AIs can have an event-queue */ if (!Company::IsValidAiID(company)) { event->Release(); return; } /* Queue the event */ Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE); Company::Get(_current_company)->ai_instance->InsertEvent(event); cur_company.Restore(); event->Release(); }
/*! * 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); }