1 /*
2 * Copyright (C) 2002-2020 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #ifndef WL_LOGIC_GAME_H
21 #define WL_LOGIC_GAME_H
22
23 #include <memory>
24
25 #include "base/md5.h"
26 #include "base/random.h"
27 #include "io/streamwrite.h"
28 #include "logic/cmd_queue.h"
29 #include "logic/editor_game_base.h"
30 #include "logic/save_handler.h"
31 #include "logic/trade_agreement.h"
32 #include "scripting/logic.h"
33
34 class InteractivePlayer;
35 struct GameSettings;
36 class GameController;
37
38 namespace Widelands {
39
40 /// How often are statistics to be sampled.
41 constexpr uint32_t kStatisticsSampleTime = 30000;
42 // See forester_cache_
43 constexpr int16_t kInvalidForesterEntry = -1;
44
45 constexpr uint32_t kScenarioDifficultyNotSet = 0;
46
47 struct Flag;
48 struct Path;
49 struct PlayerImmovable;
50 enum class IslandExploreDirection;
51 class PortDock;
52 enum class ScoutingDirection;
53 enum class SoldierPreference : uint8_t;
54 struct Ship;
55 class TrainingSite;
56 enum class StockPolicy;
57
58 enum {
59 gs_notrunning = 0, // game is being prepared
60 gs_running, // game was fully prepared at some point and is now in-game
61 gs_ending
62 };
63
64 // The entry types that are written to the syncstream
65 // The IDs are a number in the higher 4 bits and the length in bytes in the lower 4 bits
66 // Keep this synchronized with utils/syncstream/syncexcerpt-to-text.py
67 enum SyncEntry : uint8_t {
68 // Used in:
69 // game.cc Game::report_desync()
70 // Parameters:
71 // s32 id of desynced user, -1 when written on client
72 kDesync = 0x14,
73 // map_object.cc CmdDestroyMapObject::execute()
74 // u32 object serial
75 kDestroyObject = 0x24,
76 // economy.cc Economy::process_requests()
77 // u8 request type
78 // u8 request index
79 // u32 target serial
80 kProcessRequests = 0x36,
81 // economy.cc Economy::handle_active_supplies()
82 // u32 assignments size
83 kHandleActiveSupplies = 0x44,
84 // request.cc Request::start_transfer()
85 // u32 target serial
86 // u32 source(?) serial
87 kStartTransfer = 0x58,
88 // cmd_queue.cc CmdQueue::run_queue()
89 // u32 duetime
90 // u32 command id
91 kRunQueue = 0x68,
92 // game.h Game::logic_rand_seed()
93 // u32 random seed
94 kRandomSeed = 0x74,
95 // game.cc Game::logic_rand()
96 // u32 random value
97 kRandom = 0x84,
98 // map_object.cc CmdAct::execute()
99 // u32 object serial
100 // u8 object type (see map_object.h MapObjectType)
101 kCmdAct = 0x95,
102 // battle.cc Battle::Battle()
103 // u32 first soldier serial
104 // u32 second soldier serial
105 kBattle = 0xA8,
106 // bob.cc Bob::set_position()
107 // u32 bob serial
108 // s16 position x
109 // s16 position y
110 kBobSetPosition = 0xB8
111 };
112
113 class PlayerCommand;
114 class ReplayWriter;
115
116 /** class Game
117 *
118 * This class manages the entire lifetime of a game session, from creating the
119 * game and setting options, selecting maps to the actual playing phase and the
120 * final statistics screen(s).
121 */
122
123 class Game : public EditorGameBase {
124 public:
125 struct GeneralStats {
126 std::vector<uint32_t> land_size;
127 std::vector<uint32_t> nr_workers;
128 std::vector<uint32_t> nr_buildings;
129 std::vector<uint32_t> nr_wares;
130 std::vector<uint32_t> productivity;
131 std::vector<uint32_t> nr_casualties;
132 std::vector<uint32_t> nr_kills;
133 std::vector<uint32_t> nr_msites_lost;
134 std::vector<uint32_t> nr_msites_defeated;
135 std::vector<uint32_t> nr_civil_blds_lost;
136 std::vector<uint32_t> nr_civil_blds_defeated;
137 std::vector<uint32_t> miltary_strength;
138
139 std::vector<uint32_t> custom_statistic;
140 };
141 using GeneralStatsVector = std::vector<GeneralStats>;
142
143 friend class CmdQueue; // this class handles the commands
144 friend struct GameClassPacket;
145 friend struct GamePlayerInfoPacket;
146 friend struct GameLoader;
147
148 // TODO(kxq): The lifetime of game-instance is okay for this, but is this the right spot?
149 // TODO(kxq): I should find the place where LUA changes map, and clear this whenever that
150 // happens.
151 // TODO(kxq): When done, the x_check in worker.cc could be removed (or made more rare, and put
152 // under an assert as then mismatch would indicate the presence of a bug).
153 // TODO(k.halfmann): this shoud perhpas better be a map, it will be quite sparse?
154
155 /** Qualitity of terrain for tree planting normalized to int16.
156 *
157 * Indexed by MapIndex. -1 is an ivalid entry. Shared between all tribes (on the same server)
158 * will be cleared when diffrences are detected. Presently, that can only happen if terrain
159 * is changed (lua scripting). The map is sparse, lookups are fast.
160 */
161 std::vector<int16_t> forester_cache_;
162
163 Game();
164 ~Game() override;
165
166 // life cycle
167 void set_game_controller(GameController*);
168 GameController* game_controller();
169 void set_write_replay(bool wr);
170 void set_write_syncstream(bool wr);
171 void save_syncstream(bool save);
172 void init_newgame(const GameSettings&);
173 void init_savegame(const GameSettings&);
174
175 enum StartGameType { NewSPScenario, NewNonScenario, Loaded, NewMPScenario };
176
177 bool run(StartGameType,
178 const std::string& script_to_run,
179 bool replay,
180 const std::string& prefix_for_replays);
181
182 // Returns the upcasted lua interface.
183 LuaGameInterface& lua() override;
184
185 // Run a single player scenario directly via --scenario on the cmdline. Will
186 // run the 'script_to_run' after any init scripts of the map.
187 // Returns the result of run().
188 bool run_splayer_scenario_direct(const std::string& mapname, const std::string& script_to_run);
189
190 // Run a single player loaded game directly via --loadgame on the cmdline. Will
191 // run the 'script_to_run' directly after the game was loaded.
192 // Returns the result of run().
193 bool run_load_game(const std::string& filename, const std::string& script_to_run);
194
195 void postload() override;
196
197 void think() override;
198
get_replaywriter()199 ReplayWriter* get_replaywriter() {
200 return replaywriter_.get();
201 }
202
203 /**
204 * \return \c true if the game is completely loaded and running (or paused)
205 * or \c false otherwise.
206 */
is_loaded()207 bool is_loaded() {
208 return state_ == gs_running;
209 }
210
211 void cleanup_for_load() override;
212
213 // in-game logic
cmdqueue()214 const CmdQueue& cmdqueue() const {
215 return cmdqueue_;
216 }
cmdqueue()217 CmdQueue& cmdqueue() {
218 return cmdqueue_;
219 }
rng()220 const RNG& rng() const {
221 return rng_;
222 }
rng()223 RNG& rng() {
224 return rng_;
225 }
226
227 uint32_t logic_rand();
228
229 /// Generate a random location within radius from location.
230 Coords random_location(Coords location, uint8_t radius);
231
logic_rand_seed(uint32_t const seed)232 void logic_rand_seed(uint32_t const seed) {
233 rng().seed(seed);
234 syncstream().unsigned_8(SyncEntry::kRandomSeed);
235 syncstream().unsigned_32(seed);
236 }
237
238 StreamWrite& syncstream();
239 void report_sync_request();
240 void report_desync(int32_t playernumber);
241 Md5Checksum get_sync_hash() const;
242
243 void enqueue_command(Command* const);
244
245 void send_player_command(Widelands::PlayerCommand*);
246
247 void send_player_bulldoze(PlayerImmovable&, bool recurse = false);
248 void send_player_dismantle(PlayerImmovable&, bool keep_wares);
249 void send_player_build(int32_t, const Coords&, DescriptionIndex);
250 void send_player_build_flag(int32_t, const Coords&);
251 void send_player_build_road(int32_t, Path&);
252 void send_player_build_waterway(int32_t, Path&);
253 void send_player_flagaction(Flag&);
254 void send_player_start_stop_building(Building&);
255 void send_player_militarysite_set_soldier_preference(Building&, SoldierPreference preference);
256 void send_player_start_or_cancel_expedition(Building&);
257 void send_player_expedition_config(PortDock&, WareWorker, DescriptionIndex, bool);
258
259 void send_player_enhance_building(Building&, DescriptionIndex, bool keep_wares);
260 void send_player_evict_worker(Worker&);
261 void send_player_set_stock_policy(Building&, WareWorker, DescriptionIndex, StockPolicy);
262 void send_player_set_ware_priority(
263 PlayerImmovable&, int32_t type, DescriptionIndex index, int32_t prio, bool is_cs = false);
264 void send_player_set_input_max_fill(
265 PlayerImmovable&, DescriptionIndex index, WareWorker type, uint32_t, bool is_cs = false);
266 void send_player_change_training_options(TrainingSite&, TrainingAttribute, int32_t);
267 void send_player_drop_soldier(Building&, int32_t);
268 void send_player_change_soldier_capacity(Building&, int32_t);
269 void send_player_enemyflagaction(const Flag&, PlayerNumber, const std::vector<Serial>&);
270
271 void send_player_ship_scouting_direction(Ship&, WalkingDir);
272 void send_player_ship_construct_port(Ship&, Coords);
273 void send_player_ship_explore_island(Ship&, IslandExploreDirection);
274 void send_player_sink_ship(Ship&);
275 void send_player_cancel_expedition_ship(Ship&);
276 void send_player_propose_trade(const Trade& trade);
277
278 InteractivePlayer* get_ipl();
279
save_handler()280 SaveHandler& save_handler() {
281 return savehandler_;
282 }
283
get_scenario_difficulty()284 uint32_t get_scenario_difficulty() const {
285 return scenario_difficulty_;
286 }
set_scenario_difficulty(uint32_t d)287 void set_scenario_difficulty(uint32_t d) {
288 assert(scenario_difficulty_ == kScenarioDifficultyNotSet);
289 assert(d != kScenarioDifficultyNotSet);
290 scenario_difficulty_ = d;
291 }
292
293 // Statistics
get_general_statistics()294 const GeneralStatsVector& get_general_statistics() const {
295 return general_stats_;
296 }
297
298 void read_statistics(FileRead&);
299 void write_statistics(FileWrite&);
300
301 void sample_statistics();
302
303 const std::string& get_win_condition_displayname() const;
304 void set_win_condition_displayname(const std::string& name);
305
is_replay()306 bool is_replay() const {
307 return replay_;
308 }
309
is_ai_training_mode()310 bool is_ai_training_mode() const {
311 return ai_training_mode_;
312 }
313
is_auto_speed()314 bool is_auto_speed() const {
315 return auto_speed_;
316 }
317
318 void set_ai_training_mode(bool);
319
320 void set_auto_speed(bool);
321
322 // TODO(sirver,trading): document these functions once the interface settles.
323 int propose_trade(const Trade& trade);
324 void accept_trade(int trade_id);
325 void cancel_trade(int trade_id);
326
327 private:
328 void sync_reset();
329
330 MD5Checksum<StreamWrite> synchash_;
331
332 struct SyncWrapper : public StreamWrite {
SyncWrapperSyncWrapper333 SyncWrapper(Game& game, StreamWrite& target)
334 : game_(game),
335 target_(target),
336 counter_(0),
337 next_diskspacecheck_(0),
338 syncstreamsave_(false),
339 current_excerpt_id_(0) {
340 }
341
342 ~SyncWrapper() override;
343
344 /// Start dumping the entire syncstream into a file.
345 ///
346 /// Note that this file is deleted at the end of the game, unless
347 /// \ref syncstreamsave_ has been set.
348 void start_dump(const std::string& fname);
349
350 void data(void const* data, size_t size) override;
351
flushSyncWrapper352 void flush() override {
353 target_.flush();
354 }
355
356 public:
357 Game& game_;
358 StreamWrite& target_;
359 uint32_t counter_;
360 uint32_t next_diskspacecheck_;
361 std::unique_ptr<StreamWrite> dump_;
362 std::string dumpfname_;
363 bool syncstreamsave_;
364 // Use a cyclic buffer for storing parts of the syncstream
365 // Currently used buffer
366 size_t current_excerpt_id_;
367 // (Arbitrary) count of buffers
368 // Syncreports seem to be requested from the network clients every game second so this
369 // buffer should be big enough to store the last 32 seconds of the game actions leading
370 // up to the desync
371 static constexpr size_t kExcerptSize = 32;
372 // Array of byte buffers
373 // std::string is used as a binary buffer here
374 std::string excerpts_buffer_[kExcerptSize];
375 } syncwrapper_;
376
377 GameController* ctrl_;
378
379 /// Whether a replay writer should be created.
380 /// Defaults to \c true, and should only be set to \c false for playing back
381 /// replays.
382 bool writereplay_;
383
384 /// Whether a syncsteam file should be created.
385 /// Defaults to \c false, and can be set to true for network games. The file
386 /// is written only if \ref writereplay_ is true too.
387 bool writesyncstream_;
388
389 bool ai_training_mode_;
390 bool auto_speed_;
391
392 int32_t state_;
393
394 RNG rng_;
395
396 CmdQueue cmdqueue_;
397
398 SaveHandler savehandler_;
399
400 std::unique_ptr<ReplayWriter> replaywriter_;
401
402 uint32_t scenario_difficulty_;
403
404 GeneralStatsVector general_stats_;
405 int next_trade_agreement_id_ = 1;
406 // Maps from trade agreement id to the agreement.
407 std::map<int, TradeAgreement> trade_agreements_;
408
409 /// For save games and statistics generation
410 std::string win_condition_displayname_;
411 bool replay_;
412 };
413
random_location(Coords location,uint8_t radius)414 inline Coords Game::random_location(Coords location, uint8_t radius) {
415 const uint16_t s = radius * 2 + 1;
416 location.x += logic_rand() % s - radius;
417 location.y += logic_rand() % s - radius;
418 return location;
419 }
420 } // namespace Widelands
421
422 #endif // end of include guard: WL_LOGIC_GAME_H
423