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 #include "logic/editor_game_base.h"
21
22 #include <memory>
23
24 #include "base/i18n.h"
25 #include "base/macros.h"
26 #include "base/scoped_timer.h"
27 #include "base/time_string.h"
28 #include "base/wexception.h"
29 #include "economy/flag.h"
30 #include "economy/road.h"
31 #include "economy/waterway.h"
32 #include "graphic/color.h"
33 #include "graphic/road_segments.h"
34 #include "logic/filesystem_constants.h"
35 #include "logic/game.h"
36 #include "logic/game_data_error.h"
37 #include "logic/map_objects/findimmovable.h"
38 #include "logic/map_objects/map_object.h"
39 #include "logic/map_objects/tribes/battle.h"
40 #include "logic/map_objects/tribes/building.h"
41 #include "logic/map_objects/tribes/constructionsite.h"
42 #include "logic/map_objects/tribes/dismantlesite.h"
43 #include "logic/map_objects/tribes/tribe_descr.h"
44 #include "logic/map_objects/tribes/tribes.h"
45 #include "logic/map_objects/tribes/worker.h"
46 #include "logic/map_objects/world/critter.h"
47 #include "logic/map_objects/world/resource_description.h"
48 #include "logic/map_objects/world/world.h"
49 #include "logic/mapregion.h"
50 #include "logic/player.h"
51 #include "logic/playersmanager.h"
52 #include "map_io/map_saver.h"
53 #include "scripting/logic.h"
54 #include "scripting/lua_table.h"
55 #include "sound/sound_handler.h"
56 #include "ui_basic/progresswindow.h"
57 #include "wui/interactive_base.h"
58 #include "wui/interactive_gamebase.h"
59
60 namespace Widelands {
61
62 /*
63 ============
64 EditorGameBase::EditorGameBase()
65
66 initialization
67 ============
68 */
EditorGameBase(LuaInterface * lua_interface)69 EditorGameBase::EditorGameBase(LuaInterface* lua_interface)
70 : gametime_(0),
71 lua_(lua_interface),
72 player_manager_(new PlayersManager(*this)),
73 ibase_(nullptr),
74 loader_ui_(nullptr),
75 game_tips_(nullptr),
76 tmp_fs_(nullptr) {
77 if (!lua_) // TODO(SirVer): this is sooo ugly, I can't say
78 lua_.reset(new LuaEditorInterface(this));
79 }
80
~EditorGameBase()81 EditorGameBase::~EditorGameBase() {
82 delete_tempfile();
83 if (g_sh != nullptr) {
84 g_sh->remove_fx_set(SoundType::kAmbient);
85 }
86 }
87
88 /**
89 * deletes the temporary file/dir
90 * also resets the map filesystem if it points to the temporary file
91 */
delete_tempfile()92 void EditorGameBase::delete_tempfile() {
93 if (!tmp_fs_) {
94 return;
95 }
96
97 std::string fs_filename = tmp_fs_->get_basename();
98 std::string mapfs_filename = map_.filesystem()->get_basename();
99 if (mapfs_filename == fs_filename)
100 map_.reset_filesystem();
101 tmp_fs_.reset();
102 try {
103 g_fs->fs_unlink(fs_filename);
104 } catch (const std::exception& e) {
105 // if file deletion fails then we have an abandoned file lying around, but otherwise that's
106 // unproblematic
107 log("EditorGameBase::delete_tempfile: deleting temporary file/dir failed: %s\n", e.what());
108 }
109 }
110
111 /**
112 * creates a new file/dir, saves the map data, and reassigns the map filesystem
113 * does not delete the former temp file if one exists
114 * throws an exception if something goes wrong
115 */
create_tempfile_and_save_mapdata(FileSystem::Type const type)116 void EditorGameBase::create_tempfile_and_save_mapdata(FileSystem::Type const type) {
117 if (!map_.filesystem()) {
118 return;
119 }
120
121 // save map data to temporary file and reassign map fs
122 try {
123 g_fs->ensure_directory_exists(kTempFileDir);
124
125 std::string filename = kTempFileDir + g_fs->file_separator() + timestring() + "_mapdata";
126 std::string complete_filename = filename + kTempFileExtension;
127
128 // if a file with that name already exists, then try a few name modifications
129 if (g_fs->file_exists(complete_filename)) {
130 int suffix;
131 for (suffix = 0; suffix <= 9; suffix++) {
132 complete_filename = filename + "-" + std::to_string(suffix) + kTempFileExtension;
133 if (!g_fs->file_exists(complete_filename))
134 break;
135 }
136 if (suffix > 9) {
137 throw wexception(
138 "EditorGameBase::create_tempfile_and_save_mapdata(): for all considered "
139 "filenames a file already existed");
140 }
141 }
142
143 // create tmp_fs_
144 tmp_fs_.reset(g_fs->create_sub_file_system(complete_filename, type));
145
146 // save necessary map data (we actually save the whole map)
147 std::unique_ptr<Widelands::MapSaver> wms(new Widelands::MapSaver(*tmp_fs_, *this));
148 wms->save();
149
150 // swap map fs
151 std::unique_ptr<FileSystem> mapfs(tmp_fs_->make_sub_file_system("."));
152 map_.swap_filesystem(mapfs);
153 mapfs.reset();
154
155 // This is just a convenience hack:
156 // If tmp_fs_ is a zip filesystem then - because of the way zip filesystems are currently
157 // implemented -
158 // the file is still in zip mode right now, which means that the file isn't finalized yet,
159 // i.e.,
160 // not even a valid zip file until zip mode ends. To force ending the zip mode (thus
161 // finalizing
162 // the file)
163 // we simply perform a (otherwise useless) filesystem request.
164 // It's not strictly necessary, but this way we get a valid zip file immediately istead of
165 // at some unkown later point (when an unzip operation happens or a filesystem object
166 // destructs).
167 tmp_fs_->file_exists("binary");
168 } catch (const WException& e) {
169 log("EditorGameBase: saving map to temporary file failed: %s", e.what());
170 throw;
171 }
172 }
173
think()174 void EditorGameBase::think() {
175 // TODO(unknown): Get rid of this; replace by a function that just advances gametime
176 // by a given number of milliseconds
177 }
178
world() const179 const World& EditorGameBase::world() const {
180 // Const casts are evil, but this is essentially lazy evaluation and the
181 // caller should really not modify this.
182 return *const_cast<EditorGameBase*>(this)->mutable_world();
183 }
184
mutable_world()185 World* EditorGameBase::mutable_world() {
186 if (!world_) {
187 // Lazy initialization of World. We need to create the pointer to the
188 // world immediately though, because the lua scripts need to have access
189 // to world through this method already.
190 ScopedTimer timer("Loading the world took %ums");
191 world_.reset(new World());
192
193 try {
194 lua_->run_script("world/init.lua");
195 } catch (const WException& e) {
196 log("Could not read world information: %s", e.what());
197 throw;
198 }
199
200 world_->load_graphics();
201 }
202 return world_.get();
203 }
204
tribes() const205 const Tribes& EditorGameBase::tribes() const {
206 // Const casts are evil, but this is essentially lazy evaluation and the
207 // caller should really not modify this.
208 return *const_cast<EditorGameBase*>(this)->mutable_tribes();
209 }
210
mutable_tribes()211 Tribes* EditorGameBase::mutable_tribes() {
212 if (!tribes_) {
213 // We need to make sure that the world is loaded first for some attribute checks in the worker
214 // programs.
215 world();
216
217 // Lazy initialization of Tribes. We need to create the pointer to the
218 // tribe immediately though, because the lua scripts need to have access
219 // to tribes through this method already.
220 ScopedTimer timer("Loading the tribes took %ums");
221 tribes_.reset(new Tribes());
222
223 try {
224 lua_->run_script("tribes/init.lua");
225 } catch (const WException& e) {
226 log("Could not read tribes information: %s", e.what());
227 throw;
228 }
229 }
230 return tribes_.get();
231 }
232
set_ibase(InteractiveBase * const b)233 void EditorGameBase::set_ibase(InteractiveBase* const b) {
234 ibase_.reset(b);
235 }
236
get_igbase()237 InteractiveGameBase* EditorGameBase::get_igbase() {
238 return dynamic_cast<InteractiveGameBase*>(get_ibase());
239 }
240
241 /// @see PlayerManager class
remove_player(PlayerNumber plnum)242 void EditorGameBase::remove_player(PlayerNumber plnum) {
243 player_manager_->remove_player(plnum);
244 }
245
246 /// @see PlayerManager class
add_player(PlayerNumber const player_number,uint8_t const initialization_index,const std::string & tribe,const std::string & name,TeamNumber team)247 Player* EditorGameBase::add_player(PlayerNumber const player_number,
248 uint8_t const initialization_index,
249 const std::string& tribe,
250 const std::string& name,
251 TeamNumber team) {
252 return player_manager_->add_player(player_number, initialization_index, tribe, name, team);
253 }
254
get_player(const int32_t n) const255 Player* EditorGameBase::get_player(const int32_t n) const {
256 return player_manager_->get_player(n);
257 }
258
player(const int32_t n) const259 const Player& EditorGameBase::player(const int32_t n) const {
260 return player_manager_->player(n);
261 }
262
inform_players_about_ownership(MapIndex const i,PlayerNumber const new_owner)263 void EditorGameBase::inform_players_about_ownership(MapIndex const i,
264 PlayerNumber const new_owner) {
265 iterate_players_existing_const(plnum, kMaxPlayers, *this, p) {
266 Player::Field& player_field = p->fields_[i];
267 if (1 < player_field.vision) {
268 player_field.owner = new_owner;
269 }
270 }
271 }
inform_players_about_immovable(MapIndex const i,MapObjectDescr const * const descr)272 void EditorGameBase::inform_players_about_immovable(MapIndex const i,
273 MapObjectDescr const* const descr) {
274 if (!Road::is_road_descr(descr) && !Waterway::is_waterway_descr(descr))
275 iterate_players_existing_const(plnum, kMaxPlayers, *this, p) {
276 Player::Field& player_field = p->fields_[i];
277 if (1 < player_field.vision) {
278 player_field.map_object_descr = descr;
279 }
280 }
281 }
282
allocate_player_maps()283 void EditorGameBase::allocate_player_maps() {
284 iterate_players_existing(plnum, kMaxPlayers, *this, p) {
285 p->allocate_map();
286 }
287 }
288
289 /**
290 * Load and prepare detailed game and map data.
291 * This happens once just after the host has started the game / the editor has started and before
292 * the graphics are loaded.
293 */
postload()294 void EditorGameBase::postload() {
295 create_tempfile_and_save_mapdata(FileSystem::ZIP);
296
297 // Postload tribes and world
298 step_loader_ui(_("Postloading world and tribes…"));
299
300 assert(tribes_);
301 tribes_->postload();
302 assert(world_);
303 world_->postload();
304
305 for (DescriptionIndex i = 0; i < tribes_->nrtribes(); i++) {
306 const TribeDescr* tribe = tribes_->get_tribe_descr(i);
307 for (DescriptionIndex j = 0; j < world_->get_nr_resources(); j++) {
308 const ResourceDescription* res = world_->get_resource(j);
309 if (res->detectable()) {
310 // This function will throw an exception if this tribe doesn't
311 // have a high enough resource indicator for this resource
312 tribe->get_resource_indicator(res, res->max_amount());
313 }
314 }
315 // For the "none" indicator
316 tribe->get_resource_indicator(nullptr, 0);
317 }
318
319 // TODO(unknown): postload players? (maybe)
320 }
321
322 /**
323 * Load all graphics.
324 * This function needs to be called once at startup when the graphics system is ready.
325 * If the graphics system is to be replaced at runtime, the function must be called after that has
326 * happened.
327 */
load_graphics()328 void EditorGameBase::load_graphics() {
329 assert(tribes_);
330 assert(has_loader_ui());
331 step_loader_ui(_("Loading graphics"));
332 tribes_->load_graphics();
333 }
334
create_loader_ui(const std::vector<std::string> & tipstexts,bool show_game_tips,const std::string & background)335 UI::ProgressWindow& EditorGameBase::create_loader_ui(const std::vector<std::string>& tipstexts,
336 bool show_game_tips,
337 const std::string& background) {
338 assert(!has_loader_ui());
339 loader_ui_.reset(new UI::ProgressWindow(background));
340 registered_game_tips_ = tipstexts;
341 if (show_game_tips) {
342 game_tips_.reset(registered_game_tips_.empty() ?
343 nullptr :
344 new GameTips(*loader_ui_, registered_game_tips_));
345 }
346 return *loader_ui_.get();
347 }
change_loader_ui_background(const std::string & background)348 void EditorGameBase::change_loader_ui_background(const std::string& background) {
349 assert(has_loader_ui());
350 assert(game_tips_ == nullptr);
351 if (background.empty()) {
352 game_tips_.reset(registered_game_tips_.empty() ?
353 nullptr :
354 new GameTips(*loader_ui_, registered_game_tips_));
355 } else {
356 loader_ui_->set_background(background);
357 }
358 }
step_loader_ui(const std::string & text) const359 void EditorGameBase::step_loader_ui(const std::string& text) const {
360 if (loader_ui_ != nullptr) {
361 loader_ui_->step(text);
362 }
363 }
remove_loader_ui()364 void EditorGameBase::remove_loader_ui() {
365 assert(loader_ui_ != nullptr);
366 loader_ui_.reset(nullptr);
367 game_tips_.reset(nullptr);
368 registered_game_tips_.clear();
369 }
370
371 /**
372 * Instantly create a building at the given x/y location. There is no build time.
373 * \li owner is the player number of the building's owner.
374 * \li idx is the building type index.
375 * \li former_buildings is the list of former buildings
376 */
warp_building(const Coords & c,PlayerNumber const owner,DescriptionIndex const idx,FormerBuildings former_buildings)377 Building& EditorGameBase::warp_building(const Coords& c,
378 PlayerNumber const owner,
379 DescriptionIndex const idx,
380 FormerBuildings former_buildings) {
381 Player* plr = get_player(owner);
382 const TribeDescr& tribe = plr->tribe();
383 return tribe.get_building_descr(idx)->create(*this, plr, c, false, true, former_buildings);
384 }
385
386 /**
387 * Create a building site at the given x/y location for the given building type.
388 *
389 * \li idx : the building index of the building in construction
390 * \li former_buildings : the former buildings. If it is not empty, this is
391 * an enhancement.
392 */
393 Building&
warp_constructionsite(const Coords & c,PlayerNumber const owner,DescriptionIndex idx,bool loading,FormerBuildings former_buildings,const BuildingSettings * settings,std::map<DescriptionIndex,Quantity> preserved_wares)394 EditorGameBase::warp_constructionsite(const Coords& c,
395 PlayerNumber const owner,
396 DescriptionIndex idx,
397 bool loading,
398 FormerBuildings former_buildings,
399 const BuildingSettings* settings,
400 std::map<DescriptionIndex, Quantity> preserved_wares) {
401 Player* plr = get_player(owner);
402 const TribeDescr& tribe = plr->tribe();
403 ConstructionSite& b = dynamic_cast<ConstructionSite&>(
404 tribe.get_building_descr(idx)->create(*this, plr, c, true, loading, former_buildings));
405 if (settings) {
406 b.apply_settings(*settings);
407 }
408 b.add_dropout_wares(preserved_wares);
409 return b;
410 }
411
412 /**
413 * Create a dismantle site
414 * \li former_buildings : the former buildings list. This should not be empty,
415 * except during loading.
416 */
warp_dismantlesite(const Coords & c,PlayerNumber const owner,bool loading,FormerBuildings former_buildings,std::map<DescriptionIndex,Quantity> preserved_wares)417 Building& EditorGameBase::warp_dismantlesite(const Coords& c,
418 PlayerNumber const owner,
419 bool loading,
420 FormerBuildings former_buildings,
421 std::map<DescriptionIndex, Quantity> preserved_wares) {
422 Player* plr = get_player(owner);
423 const TribeDescr& tribe = plr->tribe();
424
425 BuildingDescr const* const descr =
426 tribe.get_building_descr(tribe.safe_building_index("dismantlesite"));
427
428 upcast(const DismantleSiteDescr, ds_descr, descr);
429
430 return *new DismantleSite(*ds_descr, *this, c, plr, loading, former_buildings, preserved_wares);
431 }
432
433 /**
434 * Instantly create a bob at the given x/y location.
435 */
create_bob(Coords c,const BobDescr & descr,Player * owner)436 Bob& EditorGameBase::create_bob(Coords c, const BobDescr& descr, Player* owner) {
437 return descr.create(*this, owner, c);
438 }
439
440 /**
441 * Instantly create a critter at the given x/y location.
442 *
443 */
444
create_critter(const Coords & c,DescriptionIndex const bob_type_idx,Player * owner)445 Bob& EditorGameBase::create_critter(const Coords& c,
446 DescriptionIndex const bob_type_idx,
447 Player* owner) {
448 const BobDescr* descr = dynamic_cast<const BobDescr*>(world().get_critter_descr(bob_type_idx));
449 return create_bob(c, *descr, owner);
450 }
451
create_critter(const Coords & c,const std::string & name,Player * owner)452 Bob& EditorGameBase::create_critter(const Coords& c, const std::string& name, Player* owner) {
453 const BobDescr* descr = dynamic_cast<const BobDescr*>(world().get_critter_descr(name));
454 if (descr == nullptr)
455 throw GameDataError("create_critter(%i,%i,%s,%s): critter not found", c.x, c.y, name.c_str(),
456 owner->get_name().c_str());
457 return create_bob(c, *descr, owner);
458 }
459
460 /*
461 ===============
462 Create an immovable at the given location.
463 If tribe is not zero, create a immovable of a player (not a PlayerImmovable
464 but an immovable defined by the players tribe)
465 Does not perform any placeability checks.
466 If this immovable was created by a building, 'former_building' can be set in order to display
467 information about it.
468 ===============
469 */
create_immovable(const Coords & c,DescriptionIndex const idx,MapObjectDescr::OwnerType type,Player * owner)470 Immovable& EditorGameBase::create_immovable(const Coords& c,
471 DescriptionIndex const idx,
472 MapObjectDescr::OwnerType type,
473 Player* owner) {
474 return do_create_immovable(c, idx, type, owner, nullptr);
475 }
476
create_immovable_with_name(const Coords & c,const std::string & name,MapObjectDescr::OwnerType type,Player * owner,const BuildingDescr * former_building_descr)477 Immovable& EditorGameBase::create_immovable_with_name(const Coords& c,
478 const std::string& name,
479 MapObjectDescr::OwnerType type,
480 Player* owner,
481 const BuildingDescr* former_building_descr) {
482 DescriptionIndex idx;
483 if (type == MapObjectDescr::OwnerType::kTribe) {
484 idx = tribes().immovable_index(name.c_str());
485 if (!tribes().immovable_exists(idx)) {
486 throw wexception(
487 "EditorGameBase::create_immovable_with_name(%i, %i): %s is not defined for the tribes",
488 c.x, c.y, name.c_str());
489 }
490 } else {
491 idx = world().get_immovable_index(name.c_str());
492 if (idx == INVALID_INDEX) {
493 throw wexception(
494 "EditorGameBase::create_immovable_with_name(%i, %i): %s is not defined for the world",
495 c.x, c.y, name.c_str());
496 }
497 }
498 return do_create_immovable(c, idx, type, owner, former_building_descr);
499 }
500
do_create_immovable(const Coords & c,DescriptionIndex const idx,MapObjectDescr::OwnerType type,Player * owner,const BuildingDescr * former_building_descr)501 Immovable& EditorGameBase::do_create_immovable(const Coords& c,
502 DescriptionIndex const idx,
503 MapObjectDescr::OwnerType type,
504 Player* owner,
505 const BuildingDescr* former_building_descr) {
506 const ImmovableDescr& descr =
507 *(type == MapObjectDescr::OwnerType::kTribe ? tribes().get_immovable_descr(idx) :
508 world().get_immovable_descr(idx));
509 assert(&descr);
510 inform_players_about_immovable(Map::get_index(c, map().get_width()), &descr);
511 Immovable& immovable = descr.create(*this, c, former_building_descr);
512 if (owner != nullptr) {
513 immovable.set_owner(owner);
514 }
515 return immovable;
516 }
517
518 /**
519 * Instantly create a ship at the given x/y location.
520 *
521 * idx is the bob type.
522 */
523
create_ship(const Coords & c,DescriptionIndex const ship_type_idx,Player * owner)524 Bob& EditorGameBase::create_ship(const Coords& c,
525 DescriptionIndex const ship_type_idx,
526 Player* owner) {
527 const BobDescr* descr = dynamic_cast<const BobDescr*>(tribes().get_ship_descr(ship_type_idx));
528 return create_bob(c, *descr, owner);
529 }
530
create_ship(const Coords & c,const std::string & name,Player * owner)531 Bob& EditorGameBase::create_ship(const Coords& c, const std::string& name, Player* owner) {
532 try {
533 return create_ship(c, tribes().safe_ship_index(name), owner);
534 } catch (const GameDataError& e) {
535 throw GameDataError("create_ship(%i,%i,%s,%s): ship not found: %s", c.x, c.y, name.c_str(),
536 owner->get_name().c_str(), e.what());
537 }
538 }
539
create_ferry(const Coords & c,Player * owner)540 Bob& EditorGameBase::create_ferry(const Coords& c, Player* owner) {
541 const BobDescr* descr =
542 dynamic_cast<const BobDescr*>(tribes().get_worker_descr(owner->tribe().ferry()));
543 return create_bob(c, *descr, owner);
544 }
545
546 /*
547 ================
548 Returns the correct player, creates it
549 with the scenario data when he is not yet created
550 This should only happen in the editor.
551 In the game, this is the same as get_player(). If it returns
552 zero it means that this player is disabled in the game.
553 ================
554 */
get_safe_player(PlayerNumber const n)555 Player* EditorGameBase::get_safe_player(PlayerNumber const n) {
556 return get_player(n);
557 }
558
559 /**
560 * Cleanup for load
561 *
562 * make this object ready to load new data
563 */
cleanup_for_load()564 void EditorGameBase::cleanup_for_load() {
565 step_loader_ui(_("Cleaning up for loading: Map objects (1/3)"));
566 cleanup_objects(); /// Clean all the stuff up, so we can load.
567
568 step_loader_ui(_("Cleaning up for loading: Players (2/3)"));
569 player_manager_->cleanup();
570
571 step_loader_ui(_("Cleaning up for loading: Map (3/3)"));
572 map_.cleanup();
573
574 delete_tempfile();
575 }
576
set_road(const FCoords & f,uint8_t const direction,RoadSegment const roadtype)577 void EditorGameBase::set_road(const FCoords& f,
578 uint8_t const direction,
579 RoadSegment const roadtype) {
580 const Map& m = map();
581 const Field& first_field = m[0];
582 assert(0 <= f.x);
583 assert(f.x < m.get_width());
584 assert(0 <= f.y);
585 assert(f.y < m.get_height());
586 assert(&first_field <= f.field);
587 assert(f.field < &first_field + m.max_index());
588 assert(direction == WALK_SW || direction == WALK_SE || direction == WALK_E);
589
590 if (f.field->get_road(direction) == roadtype) {
591 return;
592 }
593 f.field->set_road(direction, roadtype);
594
595 FCoords neighbour;
596 switch (direction) {
597 case WALK_SW:
598 neighbour = m.bl_n(f);
599 break;
600 case WALK_SE:
601 neighbour = m.br_n(f);
602 break;
603 case WALK_E:
604 neighbour = m.r_n(f);
605 break;
606 default:
607 NEVER_HERE();
608 }
609 MapIndex const i = f.field - &first_field;
610 MapIndex const neighbour_i = neighbour.field - &first_field;
611 iterate_players_existing_const(plnum, kMaxPlayers, *this, p) {
612 Player::Field& first_player_field = *p->fields_.get();
613 Player::Field& player_field = (&first_player_field)[i];
614 if (1 < player_field.vision || 1 < (&first_player_field)[neighbour_i].vision) {
615 switch (direction) {
616 case WALK_SE:
617 player_field.r_se = roadtype;
618 break;
619 case WALK_SW:
620 player_field.r_sw = roadtype;
621 break;
622 case WALK_E:
623 player_field.r_e = roadtype;
624 break;
625 default:
626 NEVER_HERE();
627 }
628 }
629 }
630 }
631
632 /// This unconquers an area. This is only possible, when there is a building
633 /// placed on this node.
unconquer_area(PlayerArea<Area<FCoords>> player_area,PlayerNumber const destroying_player)634 void EditorGameBase::unconquer_area(PlayerArea<Area<FCoords>> player_area,
635 PlayerNumber const destroying_player) {
636 assert(0 <= player_area.x);
637 assert(player_area.x < map().get_width());
638 assert(0 <= player_area.y);
639 assert(player_area.y < map().get_height());
640 assert(&map()[0] <= player_area.field);
641 assert(player_area.field < &map()[map().max_index()]);
642 assert(0 < player_area.player_number);
643 assert(player_area.player_number <= map().get_nrplayers());
644
645 // Here must be a building.
646 assert(
647 dynamic_cast<const Building&>(*map().get_immovable(player_area)).owner().player_number() ==
648 player_area.player_number);
649
650 // step 1: unconquer area of this building
651 do_conquer_area(player_area, false, destroying_player);
652 }
653
654 /// This conquers a given area because of a new (military) building that is set
655 /// there.
conquer_area(PlayerArea<Area<FCoords>> player_area,bool conquer_guarded_location)656 void EditorGameBase::conquer_area(PlayerArea<Area<FCoords>> player_area,
657 bool conquer_guarded_location) {
658 assert(0 <= player_area.x);
659 assert(player_area.x < map().get_width());
660 assert(0 <= player_area.y);
661 assert(player_area.y < map().get_height());
662 assert(&map()[0] <= player_area.field);
663 assert(player_area.field < &map()[map().max_index()]);
664 assert(0 < player_area.player_number);
665 assert(player_area.player_number <= map().get_nrplayers());
666
667 do_conquer_area(player_area, true, 0, conquer_guarded_location);
668 }
669
change_field_owner(const FCoords & fc,PlayerNumber const new_owner)670 void EditorGameBase::change_field_owner(const FCoords& fc, PlayerNumber const new_owner) {
671 const Field& first_field = map()[0];
672
673 PlayerNumber const old_owner = fc.field->get_owned_by();
674 if (old_owner == new_owner) {
675 return;
676 }
677
678 if (old_owner) {
679 Notifications::publish(
680 NoteFieldPossession(fc, NoteFieldPossession::Ownership::LOST, get_player(old_owner)));
681 }
682
683 fc.field->set_owned_by(new_owner);
684
685 // TODO(unknown): the player should do this when it gets the NoteFieldPossession.
686 // This means also sending a note when new_player = 0, i.e. the field is no
687 // longer owned.
688 inform_players_about_ownership(fc.field - &first_field, new_owner);
689
690 if (new_owner) {
691 Notifications::publish(
692 NoteFieldPossession(fc, NoteFieldPossession::Ownership::GAINED, get_player(new_owner)));
693 }
694 }
695
conquer_area_no_building(PlayerArea<Area<FCoords>> player_area)696 void EditorGameBase::conquer_area_no_building(PlayerArea<Area<FCoords>> player_area) {
697 assert(0 <= player_area.x);
698 assert(player_area.x < map().get_width());
699 assert(0 <= player_area.y);
700 assert(player_area.y < map().get_height());
701 assert(&map()[0] <= player_area.field);
702 assert(player_area.field < &map()[0] + map().max_index());
703 assert(0 < player_area.player_number);
704 assert(player_area.player_number <= map().get_nrplayers());
705 MapRegion<Area<FCoords>> mr(map(), player_area);
706 do {
707 change_field_owner(mr.location(), player_area.player_number);
708 } while (mr.advance(map()));
709
710 // This must reach two steps beyond the conquered area to adjust the borders
711 // of neighbour players.
712 player_area.radius += 2;
713 map_.recalc_for_field_area(*this, player_area);
714 }
715
716 /// Conquers the given area for that player; does the actual work.
717 /// Additionally, it updates the visible area for that player.
718 // TODO(unknown): this needs a more fine grained refactoring
719 // for example scripts will want to (un)conquer area of non oval shape
720 // or give area back to the neutral player (this is very important for the Lua
721 // testsuite).
do_conquer_area(PlayerArea<Area<FCoords>> player_area,bool const conquer,PlayerNumber const preferred_player,bool const conquer_guarded_location_by_superior_influence,bool const neutral_when_no_influence,bool const neutral_when_competing_influence)722 void EditorGameBase::do_conquer_area(PlayerArea<Area<FCoords>> player_area,
723 bool const conquer,
724 PlayerNumber const preferred_player,
725 bool const conquer_guarded_location_by_superior_influence,
726 bool const neutral_when_no_influence,
727 bool const neutral_when_competing_influence) {
728 assert(0 <= player_area.x);
729 assert(player_area.x < map().get_width());
730 assert(0 <= player_area.y);
731 assert(player_area.y < map().get_height());
732 const Field& first_field = map()[0];
733 assert(&first_field <= player_area.field);
734 assert(player_area.field < &first_field + map().max_index());
735 assert(0 < player_area.player_number);
736 assert(player_area.player_number <= map().get_nrplayers());
737 assert(preferred_player <= map().get_nrplayers());
738 assert(!conquer || !preferred_player);
739 Player* conquering_player = get_player(player_area.player_number);
740 MapRegion<Area<FCoords>> mr(map(), player_area);
741 do {
742 MapIndex const index = mr.location().field - &first_field;
743 MilitaryInfluence const influence =
744 map().calc_influence(mr.location(), Area<>(player_area, player_area.radius));
745
746 PlayerNumber const owner = mr.location().field->get_owned_by();
747 if (conquer) {
748 // adds the influence
749 MilitaryInfluence new_influence_modified = conquering_player->military_influence(index) +=
750 influence;
751 if (owner && !conquer_guarded_location_by_superior_influence)
752 new_influence_modified = 1;
753 if (!owner || player(owner).military_influence(index) < new_influence_modified) {
754 change_field_owner(mr.location(), player_area.player_number);
755 }
756 } else if (!(conquering_player->military_influence(index) -= influence) &&
757 owner == player_area.player_number) {
758 // The player completely lost influence over the location, which he
759 // owned. Now we must see if some other player has influence and if
760 // so, transfer the ownership to that player.
761 PlayerNumber best_player;
762 if (preferred_player && player(preferred_player).military_influence(index))
763 best_player = preferred_player;
764 else {
765 best_player = neutral_when_no_influence ? 0 : player_area.player_number;
766 MilitaryInfluence highest_military_influence = 0;
767 PlayerNumber const nr_players = map().get_nrplayers();
768 iterate_players_existing_const(p, nr_players, *this, plr) {
769 if (MilitaryInfluence const value = plr->military_influence(index)) {
770 if (value > highest_military_influence) {
771 highest_military_influence = value;
772 best_player = p;
773 } else if (value == highest_military_influence) {
774 best_player = neutral_when_competing_influence ? 0 : player_area.player_number;
775 }
776 }
777 }
778 }
779 if (best_player != player_area.player_number) {
780 change_field_owner(mr.location(), best_player);
781 }
782 }
783 } while (mr.advance(map()));
784
785 // This must reach two steps beyond the conquered area to adjust the borders
786 // of neighbour players.
787 player_area.radius += 2;
788 map_.recalc_for_field_area(*this, player_area);
789
790 // Deal with player immovables in the lost area
791 // Players are not allowed to have their immovables on their borders.
792 // Therefore the area must be enlarged before calling
793 // cleanup_playerimmovables_area, so that those new border locations are
794 // covered.
795 // TODO(SirVer): In the editor, no buildings should burn down when a military
796 // building is removed. Check this again though
797 if (is_a(Game, this)) {
798 cleanup_playerimmovables_area(player_area);
799 }
800 }
801
802 /// Makes sure that buildings cannot exist outside their owner's territory.
cleanup_playerimmovables_area(PlayerArea<Area<FCoords>> const area)803 void EditorGameBase::cleanup_playerimmovables_area(PlayerArea<Area<FCoords>> const area) {
804 std::vector<ImmovableFound> immovables;
805 std::vector<PlayerImmovable*> burnlist;
806
807 // find all immovables that need fixing
808 map_.find_immovables(*this, area, &immovables, FindImmovablePlayerImmovable());
809
810 for (const ImmovableFound& temp_imm : immovables) {
811 upcast(PlayerImmovable, imm, temp_imm.object);
812 if (!map_[temp_imm.coords].is_interior(imm->owner().player_number())) {
813 if (std::find(burnlist.begin(), burnlist.end(), imm) == burnlist.end()) {
814 burnlist.push_back(imm);
815 }
816 }
817 }
818
819 // fix all immovables
820 upcast(Game, game, this);
821 for (PlayerImmovable* temp_imm : burnlist) {
822 if (upcast(Building, building, temp_imm))
823 building->set_defeating_player(area.player_number);
824 else if (upcast(Flag, flag, temp_imm))
825 if (Building* const flag_building = flag->get_building())
826 flag_building->set_defeating_player(area.player_number);
827 if (game)
828 temp_imm->schedule_destroy(*game);
829 else
830 temp_imm->remove(*this);
831 }
832 }
833 } // namespace Widelands
834