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/player.h"
21
22 #include <cassert>
23 #include <memory>
24
25 #include <boost/algorithm/string.hpp>
26
27 #include "base/i18n.h"
28 #include "base/log.h"
29 #include "base/macros.h"
30 #include "base/warning.h"
31 #include "base/wexception.h"
32 #include "economy/economy.h"
33 #include "economy/expedition_bootstrap.h"
34 #include "economy/flag.h"
35 #include "economy/road.h"
36 #include "economy/waterway.h"
37 #include "io/fileread.h"
38 #include "io/filewrite.h"
39 #include "logic/cmd_delete_message.h"
40 #include "logic/cmd_luacoroutine.h"
41 #include "logic/game.h"
42 #include "logic/game_data_error.h"
43 #include "logic/map_objects/checkstep.h"
44 #include "logic/map_objects/findimmovable.h"
45 #include "logic/map_objects/tribes/building.h"
46 #include "logic/map_objects/tribes/constructionsite.h"
47 #include "logic/map_objects/tribes/militarysite.h"
48 #include "logic/map_objects/tribes/soldier.h"
49 #include "logic/map_objects/tribes/soldiercontrol.h"
50 #include "logic/map_objects/tribes/trainingsite.h"
51 #include "logic/map_objects/tribes/tribe_basic_info.h"
52 #include "logic/map_objects/tribes/warehouse.h"
53 #include "logic/playercommand.h"
54 #include "scripting/lua_table.h"
55 #include "sound/note_sound.h"
56 #include "sound/sound_handler.h"
57 #include "wui/interactive_player.h"
58
59 namespace {
60
terraform_for_building(Widelands::EditorGameBase & egbase,const Widelands::PlayerNumber player_number,const Widelands::Coords & location,const Widelands::BuildingDescr * descr)61 void terraform_for_building(Widelands::EditorGameBase& egbase,
62 const Widelands::PlayerNumber player_number,
63 const Widelands::Coords& location,
64 const Widelands::BuildingDescr* descr) {
65 const Widelands::Map& map = egbase.map();
66 Widelands::FCoords c[4]; // Big buildings occupy 4 locations.
67 c[0] = map.get_fcoords(location);
68 map.get_brn(c[0], &c[1]);
69 if (Widelands::BaseImmovable* const immovable = c[0].field->get_immovable())
70 immovable->remove(egbase);
71 {
72 size_t nr_locations = 1;
73 if ((descr->get_size() & Widelands::BUILDCAPS_SIZEMASK) == Widelands::BUILDCAPS_BIG) {
74 nr_locations = 4;
75 map.get_trn(c[0], &c[1]);
76 map.get_tln(c[0], &c[2]);
77 map.get_ln(c[0], &c[3]);
78 }
79 for (size_t i = 0; i < nr_locations; ++i) {
80 // Make sure that the player owns the area around.
81 egbase.conquer_area_no_building(Widelands::PlayerArea<Widelands::Area<Widelands::FCoords>>(
82 player_number, Widelands::Area<Widelands::FCoords>(c[i], 1)));
83
84 if (Widelands::BaseImmovable* const immovable = c[i].field->get_immovable())
85 immovable->remove(egbase);
86 }
87 }
88 }
89 } // namespace
90
91 namespace Widelands {
92
93 /**
94 * Find the longest possible enhancement chain leading to the given
95 * building descr. The FormerBuildings given in reference must be empty and will be
96 * filled with the BuildingDescr.
97 */
find_former_buildings(const Tribes & tribes,const Widelands::DescriptionIndex bi,Widelands::FormerBuildings * former_buildings)98 void find_former_buildings(const Tribes& tribes,
99 const Widelands::DescriptionIndex bi,
100 Widelands::FormerBuildings* former_buildings) {
101 assert(former_buildings && former_buildings->empty());
102 former_buildings->push_back(std::make_pair(bi, ""));
103
104 for (;;) {
105 Widelands::DescriptionIndex oldest_idx = former_buildings->front().first;
106 const Widelands::BuildingDescr* oldest = tribes.get_building_descr(oldest_idx);
107 if (!oldest->is_enhanced()) {
108 break;
109 }
110 for (DescriptionIndex i = 0; i < tribes.nrbuildings(); ++i) {
111 const BuildingDescr* building_descr = tribes.get_building_descr(i);
112 if (building_descr->enhancement() == oldest_idx) {
113 former_buildings->insert(former_buildings->begin(), std::make_pair(i, ""));
114 break;
115 }
116 }
117 }
118 }
119
Player(EditorGameBase & the_egbase,PlayerNumber const plnum,uint8_t const initialization_index,const TribeDescr & tribe_descr,const std::string & name)120 Player::Player(EditorGameBase& the_egbase,
121 PlayerNumber const plnum,
122 uint8_t const initialization_index,
123 const TribeDescr& tribe_descr,
124 const std::string& name)
125 : egbase_(the_egbase),
126 initialization_index_(initialization_index),
127 team_number_(0),
128 team_player_uptodate_(false),
129 see_all_(false),
130 player_number_(plnum),
131 tribe_(tribe_descr),
132 casualties_(0),
133 kills_(0),
134 msites_lost_(0),
135 msites_defeated_(0),
136 civil_blds_lost_(0),
137 civil_blds_defeated_(0),
138 ship_name_counter_(0),
139 fields_(nullptr),
140 allowed_worker_types_(the_egbase.tribes().nrworkers(), true),
141 allowed_building_types_(the_egbase.tribes().nrbuildings(), true),
142 current_produced_statistics_(the_egbase.tribes().nrwares()),
143 current_consumed_statistics_(the_egbase.tribes().nrwares()),
144 ware_productions_(the_egbase.tribes().nrwares()),
145 ware_consumptions_(the_egbase.tribes().nrwares()),
146 ware_stocks_(the_egbase.tribes().nrwares()),
147 message_fx_(SoundHandler::register_fx(SoundType::kMessage, "sound/message")),
148 attack_fx_(SoundHandler::register_fx(SoundType::kMessage, "sound/military/under_attack")),
149 occupied_fx_(SoundHandler::register_fx(SoundType::kMessage, "sound/military/site_occupied")) {
150 set_name(name);
151
152 // Disallow workers that the player's tribe doesn't have.
153 for (size_t worker_index = 0; worker_index < allowed_worker_types_.size(); ++worker_index) {
154 if (!tribe().has_worker(static_cast<DescriptionIndex>(worker_index))) {
155 allowed_worker_types_[worker_index] = false;
156 }
157 }
158
159 // Disallow buildings that the player's tribe doesn't have and
160 // that aren't militarysites that the tribe could conquer.
161 for (size_t i = 0; i < allowed_building_types_.size(); ++i) {
162 const DescriptionIndex& building_index = static_cast<DescriptionIndex>(i);
163 const BuildingDescr& descr = *tribe().get_building_descr(building_index);
164 if (!tribe().has_building(building_index) && descr.type() != MapObjectType::MILITARYSITE) {
165 allowed_building_types_[i] = false;
166 }
167 }
168
169 // Subscribe to NoteImmovables.
170 immovable_subscriber_ =
171 Notifications::subscribe<NoteImmovable>([this](const NoteImmovable& note) {
172 if (note.pi->owner().player_number() == player_number()) {
173 if (upcast(Building, building, note.pi))
174 update_building_statistics(*building, note.ownership);
175 }
176 });
177
178 // Subscribe to NoteFieldTerrainChanged.
179 field_terrain_changed_subscriber_ = Notifications::subscribe<NoteFieldTerrainChanged>(
180 [this](const NoteFieldTerrainChanged& note) {
181 if (vision(note.map_index) > 1) {
182 rediscover_node(egbase().map(), note.fc);
183 }
184 });
185
186 // Populating remaining_shipnames vector
187 for (const auto& shipname : tribe_descr.get_ship_names()) {
188 remaining_shipnames_.insert(shipname);
189 }
190 }
191
~Player()192 Player::~Player() {
193 }
194
create_default_infrastructure()195 void Player::create_default_infrastructure() {
196 const Map& map = egbase().map();
197 if (map.get_starting_pos(player_number_)) {
198 const Widelands::TribeBasicInfo::Initialization& initialization =
199 tribe().initialization(initialization_index_);
200
201 Game& game = dynamic_cast<Game&>(egbase());
202
203 // Run the corresponding script
204 std::unique_ptr<LuaTable> table(game.lua().run_script(initialization.script));
205 table->do_not_warn_about_unaccessed_keys();
206 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");
207 cr->push_arg(this);
208 game.enqueue_command(new CmdLuaCoroutine(game.get_gametime(), std::move(cr)));
209
210 // Check if other starting positions are shared in and initialize them as well
211 for (uint8_t n = 0; n < further_shared_in_player_.size(); ++n) {
212 Coords const further_pos = map.get_starting_pos(further_shared_in_player_.at(n));
213
214 // Run the corresponding script
215 std::unique_ptr<LuaCoroutine> ncr =
216 game.lua()
217 .run_script(tribe().initialization(further_initializations_.at(n)).script)
218 ->get_coroutine("func");
219 ncr->push_arg(this);
220 ncr->push_arg(further_pos);
221 game.enqueue_command(new CmdLuaCoroutine(game.get_gametime(), std::move(ncr)));
222 }
223 } else
224 throw WLWarning(_("Missing starting position"),
225 _("Widelands could not start the game, because player %u has "
226 "no starting position.\n"
227 "You can manually add a starting position with the Widelands "
228 "Editor to fix this problem."),
229 static_cast<unsigned int>(player_number_));
230 }
231
232 /**
233 * Allocate the fields array that contains player-specific field information.
234 */
allocate_map()235 void Player::allocate_map() {
236 const Map& map = egbase().map();
237 assert(map.get_width());
238 assert(map.get_height());
239 fields_.reset(new Field[map.max_index()]);
240 }
241
242 /**
243 * Assign the player the given team number.
244 */
set_team_number(TeamNumber team)245 void Player::set_team_number(TeamNumber team) {
246 team_number_ = team;
247 team_player_uptodate_ = false;
248 }
249
250 /**
251 * Returns whether this player and the given other player can attack
252 * each other.
253 */
is_hostile(const Player & other) const254 bool Player::is_hostile(const Player& other) const {
255 return &other != this && (!team_number_ || team_number_ != other.team_number_) &&
256 !is_attack_forbidden(other.player_number());
257 }
258
is_defeated() const259 bool Player::is_defeated() const {
260 for (const auto& economy : economies()) {
261 if (!economy.second->warehouses().empty()) {
262 return false;
263 }
264 }
265 return true;
266 }
267
initialize()268 void Player::AiPersistentState::initialize() {
269 colony_scan_area = kColonyScanStartArea;
270 trees_around_cutters = 0;
271 last_attacked_player = std::numeric_limits<int16_t>::max();
272 expedition_start_time = kNoExpedition;
273 ships_utilization = 200;
274 no_more_expeditions = false;
275 target_military_score = 100;
276 least_military_score = 0;
277 ai_productionsites_ratio = std::rand() % 5 + 7;
278 ai_personality_mil_upper_limit = 100;
279
280 // all zeroes
281 assert(neuron_weights.size() == Widelands::Player::AiPersistentState::kNeuronPoolSize);
282 assert(neuron_functs.size() == Widelands::Player::AiPersistentState::kNeuronPoolSize);
283 assert(f_neurons.size() == Widelands::Player::AiPersistentState::kFNeuronPoolSize);
284 assert(magic_numbers.size() == Widelands::Player::AiPersistentState::kMagicNumbersSize);
285 for (size_t i = 0; i < neuron_weights.size(); ++i) {
286 neuron_weights.at(i) = 0;
287 }
288 for (size_t i = 0; i < neuron_functs.size(); ++i) {
289 neuron_functs.at(i) = 0;
290 }
291 for (size_t i = 0; i < f_neurons.size(); ++i) {
292 f_neurons.at(i) = 0;
293 }
294 for (size_t i = 0; i < magic_numbers.size(); ++i) {
295 magic_numbers.at(i) = 0;
296 }
297 remaining_basic_buildings.clear();
298
299 initialized = true;
300 }
301
302 /**
303 * Updates the vector containing allied players
304 */
update_team_players()305 void Player::update_team_players() {
306 team_player_.clear();
307 team_player_uptodate_ = true;
308
309 if (!team_number_)
310 return;
311
312 for (PlayerNumber i = 1; i <= kMaxPlayers; ++i) {
313 Player* other = egbase().get_player(i);
314 if (!other)
315 continue;
316 if (other == this)
317 continue;
318 if (team_number_ == other->team_number_)
319 team_player_.push_back(other);
320 }
321 }
322
323 /*
324 * Plays the corresponding sound when a message is received and if sound is
325 * enabled.
326 */
play_message_sound(const Message * message)327 void Player::play_message_sound(const Message* message) {
328 if (g_sh->is_sound_enabled(SoundType::kMessage)) {
329 FxId fx;
330 switch (message->type()) {
331 case Message::Type::kEconomySiteOccupied:
332 fx = occupied_fx_;
333 break;
334 case Message::Type::kWarfareUnderAttack:
335 fx = attack_fx_;
336 break;
337 default:
338 fx = message_fx_;
339 }
340 Notifications::publish(
341 NoteSound(SoundType::kMessage, fx, message->position(), kFxPriorityAlwaysPlay));
342 }
343 }
344
add_message(Game & game,std::unique_ptr<Message> new_message,bool const popup)345 MessageId Player::add_message(Game& game, std::unique_ptr<Message> new_message, bool const popup) {
346 MessageId id = get_messages()->add_message(std::move(new_message));
347 const Message* message = messages()[id];
348
349 // MapObject connection
350 if (message->serial() > 0) {
351 MapObject* mo = egbase().objects().get_object(message->serial());
352 mo->removed.connect([this, id](unsigned) { message_object_removed(id); });
353 }
354
355 // Sound & popup
356 if (InteractivePlayer* const iplayer = game.get_ipl()) {
357 if (&iplayer->player() == this) {
358 play_message_sound(message);
359 if (popup)
360 iplayer->popup_message(id, *message);
361 }
362 }
363
364 return id;
365 }
366
add_message_with_timeout(Game & game,std::unique_ptr<Message> message,uint32_t const timeout,uint32_t const radius)367 MessageId Player::add_message_with_timeout(Game& game,
368 std::unique_ptr<Message> message,
369 uint32_t const timeout,
370 uint32_t const radius) {
371 const Map& map = game.map();
372 uint32_t const gametime = game.get_gametime();
373 Coords const position = message->position();
374 for (const auto& tmp_message : messages()) {
375 if (tmp_message.second->type() == message->type() &&
376 tmp_message.second->sub_type() == message->sub_type() &&
377 gametime < tmp_message.second->sent() + timeout &&
378 map.calc_distance(tmp_message.second->position(), position) <= radius) {
379 return MessageId::null();
380 }
381 }
382 return add_message(game, std::move(message));
383 }
384
message_object_removed(MessageId message_id) const385 void Player::message_object_removed(MessageId message_id) const {
386 // Send delete command
387 upcast(Game, game, &egbase_);
388 if (!game) {
389 return;
390 }
391
392 game->cmdqueue().enqueue(new CmdDeleteMessage(game->get_gametime(), player_number_, message_id));
393 }
394
ships() const395 const std::set<Serial>& Player::ships() const {
396 return ships_;
397 }
add_ship(Serial ship)398 void Player::add_ship(Serial ship) {
399 ships_.insert(ship);
400 }
remove_ship(Serial ship)401 void Player::remove_ship(Serial ship) {
402 auto it = ships_.find(ship);
403 if (it != ships_.end()) {
404 ships_.erase(it);
405 }
406 }
407
408 /*
409 ===============
410 Return filtered buildcaps that take the player's territory into account.
411 ===============
412 */
get_buildcaps(const FCoords & fc) const413 NodeCaps Player::get_buildcaps(const FCoords& fc) const {
414 const Map& map = egbase().map();
415 uint8_t buildcaps = fc.field->nodecaps();
416
417 if (!fc.field->is_interior(player_number_))
418 buildcaps = 0;
419
420 // Check if a building's flag can't be build due to ownership
421 else if (buildcaps & BUILDCAPS_BUILDINGMASK) {
422 FCoords flagcoords;
423 map.get_brn(fc, &flagcoords);
424 if (!flagcoords.field->is_interior(player_number_))
425 buildcaps &= ~BUILDCAPS_BUILDINGMASK;
426
427 // Prevent big buildings that would swell over borders.
428 if ((buildcaps & BUILDCAPS_BIG) == BUILDCAPS_BIG &&
429 (!map.tr_n(fc).field->is_interior(player_number_) ||
430 !map.tl_n(fc).field->is_interior(player_number_) ||
431 !map.l_n(fc).field->is_interior(player_number_)))
432 buildcaps &= ~BUILDCAPS_SMALL;
433 }
434
435 return static_cast<NodeCaps>(buildcaps);
436 }
437
438 /**
439 * Build a flag, checking that it's legal to do so. Returns
440 * the flag in case of success, else returns 0;
441 */
build_flag(const Coords & c)442 Flag* Player::build_flag(const Coords& c) {
443 int32_t buildcaps = get_buildcaps(egbase().map().get_fcoords(c));
444
445 if (buildcaps & BUILDCAPS_FLAG)
446 return new Flag(egbase(), this, c);
447 return nullptr;
448 }
449
force_flag(const FCoords & c)450 Flag& Player::force_flag(const FCoords& c) {
451 log("Forcing flag at (%i, %i)\n", c.x, c.y);
452 const Map& map = egbase().map();
453 if (BaseImmovable* const immovable = c.field->get_immovable()) {
454 if (upcast(Flag, existing_flag, immovable)) {
455 if (existing_flag->get_owner() == this)
456 return *existing_flag;
457 } else if (!dynamic_cast<RoadBase const*>(immovable)) // A road or waterway is OK
458 immovable->remove(egbase()); // Make room for the flag.
459 }
460 MapRegion<Area<FCoords>> mr(map, Area<FCoords>(c, 1));
461 do
462 if (upcast(Flag, flag, mr.location().field->get_immovable()))
463 flag->remove(egbase()); // Remove all flags that are too close.
464 while (mr.advance(map));
465
466 // Make sure that the player owns the area around.
467 egbase().conquer_area_no_building(
468 PlayerArea<Area<FCoords>>(player_number(), Area<FCoords>(c, 1)));
469 return *new Flag(egbase(), this, c);
470 }
471
472 /*
473 ===============
474 Build a road along the given path.
475 Perform sanity checks (ownership, flags).
476
477 Note: the diagnostic log messages aren't exactly errors. They might happen
478 in some situations over the network.
479 ===============
480 */
build_road(const Path & path)481 Road* Player::build_road(const Path& path) {
482 const Map& map = egbase().map();
483 FCoords fc = map.get_fcoords(path.get_start());
484 if (upcast(Flag, start, fc.field->get_immovable())) {
485 if (upcast(Flag, end, map.get_immovable(path.get_end()))) {
486
487 // Verify ownership of the path.
488 const int32_t laststep = path.get_nsteps() - 1;
489 for (int32_t i = 0; i < laststep; ++i) {
490 fc = map.get_neighbour(fc, path[i]);
491
492 if (BaseImmovable* const imm = fc.field->get_immovable())
493 if (imm->get_size() >= BaseImmovable::SMALL) {
494 return nullptr;
495 }
496 if (!(get_buildcaps(fc) & MOVECAPS_WALK)) {
497 log("%i: building road, unwalkable\n", player_number());
498 return nullptr;
499 }
500 }
501 return &Road::create(egbase(), *start, *end, path);
502 } else
503 log("%i: building road, missed end flag\n", player_number());
504 } else
505 log("%i: building road, missed start flag\n", player_number());
506
507 return nullptr;
508 }
509
force_road(const Path & path)510 Road& Player::force_road(const Path& path) {
511 const Map& map = egbase().map();
512 FCoords c = map.get_fcoords(path.get_start());
513 Flag& start = force_flag(c);
514 Flag& end = force_flag(map.get_fcoords(path.get_end()));
515
516 Path::StepVector::size_type const laststep = path.get_nsteps() - 1;
517 for (Path::StepVector::size_type i = 0; i < laststep; ++i) {
518 c = map.get_neighbour(c, path[i]);
519 log("Clearing for road at (%i, %i)\n", c.x, c.y);
520
521 // Make sure that the player owns the area around.
522 dynamic_cast<Game&>(egbase()).conquer_area_no_building(
523 PlayerArea<Area<FCoords>>(player_number(), Area<FCoords>(c, 1)));
524
525 if (BaseImmovable* const immovable = c.field->get_immovable()) {
526 assert(immovable != &start);
527 assert(immovable != &end);
528 immovable->remove(egbase());
529 }
530 }
531 return Road::create(egbase(), start, end, path);
532 }
533
build_waterway(const Path & path)534 Waterway* Player::build_waterway(const Path& path) {
535 const Map& map = egbase().map();
536
537 if (path.get_nsteps() > map.get_waterway_max_length()) {
538 log("%d: Refused to build a waterway because it is too long. Permitted length %d, actual "
539 "length %" PRIuS ".",
540 static_cast<unsigned int>(player_number()), map.get_waterway_max_length(),
541 path.get_nsteps());
542 return nullptr;
543 }
544
545 FCoords fc = map.get_fcoords(path.get_start());
546 if (upcast(Flag, start, fc.field->get_immovable())) {
547 if (upcast(Flag, end, map.get_immovable(path.get_end()))) {
548 // Verify ownership of the path.
549 const int32_t laststep = path.get_nsteps() - 1;
550 for (int32_t i = 0; i < laststep; ++i) {
551 fc = map.get_neighbour(fc, path[i]);
552
553 if (BaseImmovable* const imm = fc.field->get_immovable()) {
554 if (imm->get_size() >= BaseImmovable::SMALL) {
555 return nullptr;
556 }
557 }
558 if (!CheckStepFerry(egbase()).reachable_dest(map, fc)) {
559 log("%i: building waterway aborted, unreachable for ferries\n",
560 static_cast<unsigned int>(player_number()));
561 return nullptr;
562 }
563 }
564 return &Waterway::create(egbase(), *start, *end, path);
565 } else {
566 log("%i: building waterway aborted, missing end flag\n",
567 static_cast<unsigned int>(player_number()));
568 }
569 } else {
570 log("%i: building waterway aborted, missing start flag\n",
571 static_cast<unsigned int>(player_number()));
572 }
573 return nullptr;
574 }
575
force_waterway(const Path & path)576 Waterway& Player::force_waterway(const Path& path) {
577 const Map& map = egbase().map();
578 FCoords c = map.get_fcoords(path.get_start());
579 Flag& start = force_flag(c);
580 Flag& end = force_flag(map.get_fcoords(path.get_end()));
581
582 Path::StepVector::size_type const laststep = path.get_nsteps() - 1;
583 for (Path::StepVector::size_type i = 0; i < laststep; ++i) {
584 c = map.get_neighbour(c, path[i]);
585 log("Clearing for waterway at (%i, %i)\n", c.x, c.y);
586
587 // Make sure that the player owns the area around.
588 dynamic_cast<Game&>(egbase()).conquer_area_no_building(
589 PlayerArea<Area<FCoords>>(player_number(), Area<FCoords>(c, 1)));
590
591 if (BaseImmovable* const immovable = c.field->get_immovable()) {
592 assert(immovable != &start);
593 assert(immovable != &end);
594 immovable->remove(egbase());
595 }
596 }
597 return Waterway::create(egbase(), start, end, path);
598 }
599
force_building(Coords const location,const FormerBuildings & former_buildings)600 Building& Player::force_building(Coords const location, const FormerBuildings& former_buildings) {
601 const Map& map = egbase().map();
602 DescriptionIndex idx = former_buildings.back().first;
603 const BuildingDescr* descr = egbase().tribes().get_building_descr(idx);
604 terraform_for_building(egbase(), player_number(), location, descr);
605 FCoords flag_loc;
606 map.get_brn(map.get_fcoords(location), &flag_loc);
607 force_flag(flag_loc);
608
609 return descr->create(egbase(), this, map.get_fcoords(location), false, false, former_buildings);
610 }
611
force_csite(Coords const location,DescriptionIndex b_idx,const FormerBuildings & former_buildings)612 Building& Player::force_csite(Coords const location,
613 DescriptionIndex b_idx,
614 const FormerBuildings& former_buildings) {
615 EditorGameBase& eg = egbase();
616 const Map& map = eg.map();
617 const Tribes& tribes = eg.tribes();
618 const PlayerNumber pn = player_number();
619
620 if (!former_buildings.empty()) {
621 DescriptionIndex idx = former_buildings.back().first;
622 const BuildingDescr* descr = tribes.get_building_descr(idx);
623 terraform_for_building(eg, pn, location, descr);
624 }
625 FCoords flag_loc;
626 map.get_brn(map.get_fcoords(location), &flag_loc);
627 force_flag(flag_loc);
628
629 terraform_for_building(eg, pn, location, tribes.get_building_descr(b_idx));
630
631 return eg.warp_constructionsite(
632 map.get_fcoords(location), player_number_, b_idx, false, former_buildings);
633 }
634
635 /*
636 ===============
637 Place a construction site or building, checking that it's legal to do so.
638 ===============
639 */
build(Coords c,DescriptionIndex const idx,bool constructionsite,FormerBuildings & former_buildings)640 Building* Player::build(Coords c,
641 DescriptionIndex const idx,
642 bool constructionsite,
643 FormerBuildings& former_buildings) {
644 // Validate building type
645 if (!tribe().has_building(idx)) {
646 return nullptr;
647 }
648
649 const BuildingDescr* descr = egbase().tribes().get_building_descr(idx);
650
651 if (!descr->is_buildable()) {
652 return nullptr;
653 }
654
655 // Validate build position
656 const Map& map = egbase().map();
657 map.normalize_coords(c);
658 const FCoords fc = map.get_fcoords(c);
659 const FCoords brn = map.br_n(fc);
660 if (!fc.field->is_interior(player_number()) || !brn.field->is_interior(player_number())) {
661 return nullptr;
662 }
663 if (descr->get_size() >= BaseImmovable::BIG &&
664 !((map.l_n(fc).field->is_interior(player_number())) &&
665 (map.tr_n(fc).field->is_interior(player_number())) &&
666 (map.tl_n(fc).field->is_interior(player_number())))) {
667 return nullptr;
668 }
669
670 if (descr->get_built_over_immovable() != INVALID_INDEX &&
671 !(fc.field->get_immovable() &&
672 fc.field->get_immovable()->has_attribute(descr->get_built_over_immovable()))) {
673 return nullptr;
674 }
675
676 const NodeCaps buildcaps = descr->get_built_over_immovable() == INVALID_INDEX ?
677 get_buildcaps(fc) :
678 map.get_max_nodecaps(egbase(), fc);
679 if (descr->get_ismine()) {
680 if (!(buildcaps & BUILDCAPS_MINE)) {
681 return nullptr;
682 }
683 } else {
684 if ((buildcaps & BUILDCAPS_SIZEMASK) < descr->get_size() - BaseImmovable::SMALL + 1) {
685 return nullptr;
686 }
687 if (descr->get_isport() && !(buildcaps & BUILDCAPS_PORT)) {
688 return nullptr;
689 }
690 }
691 if (!(brn.field->get_immovable() &&
692 brn.field->get_immovable()->descr().type() == MapObjectType::FLAG) &&
693 !(get_buildcaps(brn) & BUILDCAPS_FLAG)) {
694 return nullptr;
695 }
696
697 if (constructionsite) {
698 return &egbase().warp_constructionsite(c, player_number_, idx, false, former_buildings);
699 } else {
700 return &descr->create(egbase(), this, c, false, false, former_buildings);
701 }
702 }
703
704 /*
705 ===============
706 Bulldoze the given road, waterway, flag or building.
707 ===============
708 */
bulldoze(PlayerImmovable & imm,bool const recurse)709 void Player::bulldoze(PlayerImmovable& imm, bool const recurse) {
710 std::vector<OPtr<PlayerImmovable>> bulldozelist;
711 bulldozelist.push_back(&imm);
712
713 while (!bulldozelist.empty()) {
714 PlayerImmovable* immovable = bulldozelist.back().get(egbase());
715 bulldozelist.pop_back();
716 if (!immovable)
717 continue;
718
719 // General security check
720 if (immovable->get_owner() != this)
721 return;
722
723 // Destroy, after extended security check
724 if (upcast(Building, building, immovable)) {
725 if (!(building->get_playercaps() & Building::PCap_Bulldoze))
726 return;
727
728 Flag& flag = building->base_flag();
729 building->destroy(egbase());
730 // Now imm and building are dangling reference/pointer! Do not use!
731
732 if (recurse && flag.is_dead_end())
733 bulldozelist.push_back(&flag);
734 } else if (upcast(Flag, flag, immovable)) {
735 if (Building* const flagbuilding = flag->get_building())
736 if (!(flagbuilding->get_playercaps() & Building::PCap_Bulldoze)) {
737 log("Player trying to rip flag (%u) with undestroyable "
738 "building (%u)\n",
739 flag->serial(), flagbuilding->serial());
740 return;
741 }
742
743 OPtr<Flag> flagcopy = flag;
744 if (recurse) {
745 for (uint8_t primary_road_id = 6; primary_road_id; --primary_road_id) {
746 // Recursive bulldoze calls may cause flag to disappear
747 if (!flagcopy.get(egbase()))
748 return;
749
750 if (RoadBase* const primary_road = flag->get_roadbase(primary_road_id)) {
751 Flag& primary_start = primary_road->get_flag(RoadBase::FlagStart);
752 Flag& primary_other = flag == &primary_start ?
753 primary_road->get_flag(RoadBase::FlagEnd) :
754 primary_start;
755 primary_road->destroy(egbase());
756 log("destroying road/waterway from (%i, %i) going in dir %u\n",
757 flag->get_position().x, flag->get_position().y, primary_road_id);
758 // The primary road is gone. Now see if the flag at the other
759 // end of it is a dead-end.
760 if (primary_other.is_dead_end())
761 bulldozelist.push_back(&primary_other);
762 }
763 }
764 }
765
766 // Recursive bulldoze calls may cause flag to disappear
767 if (flagcopy.get(egbase()))
768 flag->destroy(egbase());
769 } else if (upcast(RoadBase, road, immovable)) {
770 Flag& start = road->get_flag(RoadBase::FlagStart);
771 Flag& end = road->get_flag(RoadBase::FlagEnd);
772
773 road->destroy(egbase());
774 // Now imm and road are dangling reference/pointer! Do not use!
775
776 if (recurse) {
777 // Destroy all roads and waterways between the flags, not just selected
778 while (RoadBase* const r = start.get_roadbase(end))
779 r->destroy(egbase());
780
781 OPtr<Flag> endcopy = &end;
782 if (start.is_dead_end())
783 bulldozelist.push_back(&start);
784 // At this point, end may have become dangling
785 if (Flag* pend = endcopy.get(egbase())) {
786 if (pend->is_dead_end())
787 bulldozelist.push_back(&end);
788 }
789 }
790 } else
791 throw wexception("Player::bulldoze(%u): bad immovable type", immovable->serial());
792 }
793 }
794
start_stop_building(PlayerImmovable & imm)795 void Player::start_stop_building(PlayerImmovable& imm) {
796 if (imm.get_owner() == this)
797 if (upcast(ProductionSite, productionsite, &imm))
798 productionsite->set_stopped(!productionsite->is_stopped());
799 }
800
start_or_cancel_expedition(Warehouse & wh)801 void Player::start_or_cancel_expedition(Warehouse& wh) {
802 if (wh.get_owner() == this)
803 if (PortDock* pd = wh.get_portdock()) {
804 if (pd->expedition_started()) {
805 upcast(Game, game, &egbase());
806 pd->cancel_expedition(*game);
807 } else
808 pd->start_expedition();
809 }
810 }
811
military_site_set_soldier_preference(PlayerImmovable & imm,SoldierPreference soldier_preference)812 void Player::military_site_set_soldier_preference(PlayerImmovable& imm,
813 SoldierPreference soldier_preference) {
814 if (imm.get_owner() == this)
815 if (upcast(MilitarySite, milsite, &imm))
816 milsite->set_soldier_preference(soldier_preference);
817 }
818
819 /*
820 * enhance this building, remove it, but give the constructionsite
821 * an idea of enhancing
822 */
enhance_building(Building * building,DescriptionIndex const index_of_new_building,bool keep_wares)823 void Player::enhance_building(Building* building,
824 DescriptionIndex const index_of_new_building,
825 bool keep_wares) {
826 enhance_or_dismantle(building, index_of_new_building, keep_wares);
827 }
828
829 /*
830 * rip this building down, but slowly: a builder will take it gradually
831 * apart.
832 */
dismantle_building(Building * building,bool keep_wares)833 void Player::dismantle_building(Building* building, bool keep_wares) {
834 enhance_or_dismantle(building, INVALID_INDEX, keep_wares);
835 }
enhance_or_dismantle(Building * building,DescriptionIndex const index_of_new_building,bool keep_wares)836 void Player::enhance_or_dismantle(Building* building,
837 DescriptionIndex const index_of_new_building,
838 bool keep_wares) {
839 if (building->get_owner() == this &&
840 (index_of_new_building == INVALID_INDEX ||
841 building->descr().enhancement() == index_of_new_building)) {
842 FormerBuildings former_buildings = building->get_former_buildings();
843 const Coords position = building->get_position();
844
845 // Get wares, workers, and soldiers
846 // Make copies of the vectors, because the originals are destroyed with
847 // the building.
848 std::vector<Worker*> workers;
849 std::map<DescriptionIndex, Quantity> wares;
850 auto add_to_wares = [&wares](DescriptionIndex di, Quantity add) {
851 if (add == 0) {
852 return;
853 }
854 auto it = wares.find(di);
855 if (it == wares.end()) {
856 wares[di] = add;
857 } else {
858 it->second += add;
859 }
860 };
861
862 if (upcast(Warehouse, wh, building)) {
863 workers = wh->get_incorporated_workers();
864 if (keep_wares) {
865 for (DescriptionIndex di = wh->get_wares().get_nrwareids(); di; --di) {
866 wares[di - 1] = wh->get_wares().stock(di - 1);
867 }
868 if (PortDock* pd = wh->get_portdock()) {
869 for (DescriptionIndex di : tribe().wares()) {
870 add_to_wares(di, pd->count_waiting(wwWARE, di));
871 }
872 if (ExpeditionBootstrap* x = pd->expedition_bootstrap()) {
873 for (const InputQueue* q : x->queues(true)) {
874 if (q->get_type() == wwWARE) {
875 add_to_wares(q->get_index(), q->get_filled());
876 }
877 }
878 }
879 }
880 }
881 } else {
882 workers = building->get_workers();
883 if (keep_wares) {
884 // TODO(Nordfriese): Add support for markets?
885 if (upcast(ProductionSite, ps, building)) {
886 for (const InputQueue* q : ps->inputqueues()) {
887 if (q->get_type() == wwWARE) {
888 auto it = wares.find(q->get_index());
889 if (it == wares.end()) {
890 wares[q->get_index()] = q->get_filled();
891 } else {
892 it->second += q->get_filled();
893 }
894 }
895 }
896 }
897 }
898 }
899 assert(keep_wares || wares.empty());
900
901 const BuildingSettings* settings = nullptr;
902 if (index_of_new_building != INVALID_INDEX) {
903 settings = building->create_building_settings();
904 // For enhancing, register whether the window was open
905 Notifications::publish(NoteBuilding(building->serial(), NoteBuilding::Action::kStartWarp));
906 }
907
908 building->remove(egbase()); // no fire or stuff
909 // Hereafter the old building does not exist and building is a dangling
910 // pointer.
911
912 if (index_of_new_building != INVALID_INDEX) {
913 building = &egbase().warp_constructionsite(position, player_number_, index_of_new_building,
914 false, former_buildings, settings, wares);
915 } else {
916 building =
917 &egbase().warp_dismantlesite(position, player_number_, false, former_buildings, wares);
918 }
919
920 // Open the new building window if needed
921 Notifications::publish(NoteBuilding(building->serial(), NoteBuilding::Action::kFinishWarp));
922
923 // Hereafter building points to the new building.
924
925 // Reassign the workers and soldiers.
926 // Note that this will make sure they stay within the economy;
927 // However, they are no longer associated with the building as
928 // workers of that buiding, which is why they will leave for a
929 // warehouse.
930 for (Worker* temp_worker : workers) {
931 temp_worker->set_location(building);
932 }
933 }
934 }
935
936 /*
937 ===============
938 Perform an action on the given flag.
939 ===============
940 */
flagaction(Flag & flag)941 void Player::flagaction(Flag& flag) {
942 if (flag.get_owner() == this) { // Additional security check.
943 flag.add_flag_job(dynamic_cast<Game&>(egbase()), tribe().geologist(), "expedition");
944 }
945 }
946
allow_worker_type(DescriptionIndex const i,bool const allow)947 void Player::allow_worker_type(DescriptionIndex const i, bool const allow) {
948 assert(i < static_cast<int>(allowed_worker_types_.size()));
949 assert(!allow || tribe().get_worker_descr(i)->is_buildable());
950 allowed_worker_types_[i] = allow;
951 }
952
953 /*
954 * allow building
955 *
956 * Disable or enable a building for a player
957 */
allow_building_type(DescriptionIndex const i,bool const allow)958 void Player::allow_building_type(DescriptionIndex const i, bool const allow) {
959 assert(i < allowed_building_types_.size());
960 allowed_building_types_[i] = allow;
961 }
962
963 /*
964 * Economy stuff below
965 */
create_economy(WareWorker type)966 Economy* Player::create_economy(WareWorker type) {
967 std::unique_ptr<Economy> eco(new Economy(*this, type));
968 const Serial serial = eco->serial();
969
970 assert(economies_.count(serial) == 0);
971 economies_.emplace(std::make_pair(serial, std::move(eco)));
972 assert(economies_.at(serial)->serial() == serial);
973 assert(economies_.count(serial) == 1);
974
975 return get_economy(serial);
976 }
977
create_economy(Serial serial,WareWorker type)978 Economy* Player::create_economy(Serial serial, WareWorker type) {
979 std::unique_ptr<Economy> eco(new Economy(*this, serial, type));
980
981 assert(economies_.count(serial) == 0);
982 economies_.emplace(std::make_pair(serial, std::move(eco)));
983 assert(economies_.at(serial)->serial() == serial);
984 assert(economies_.count(serial) == 1);
985
986 return get_economy(serial);
987 }
988
remove_economy(Serial serial)989 void Player::remove_economy(Serial serial) {
990 assert(has_economy(serial));
991 economies_.erase(economies_.find(serial));
992 assert(!has_economy(serial));
993 }
994
economies() const995 const std::map<Serial, std::unique_ptr<Economy>>& Player::economies() const {
996 return economies_;
997 }
998
get_economy(Widelands::Serial serial) const999 Economy* Player::get_economy(Widelands::Serial serial) const {
1000 if (economies_.count(serial) == 0) {
1001 return nullptr;
1002 }
1003 return economies_.at(serial).get();
1004 }
1005
has_economy(Widelands::Serial serial) const1006 bool Player::has_economy(Widelands::Serial serial) const {
1007 return economies_.count(serial) != 0;
1008 }
1009
1010 /************ Military stuff **********/
1011
1012 /*
1013 ==========
1014 Change the training priotity values
1015 ==========
1016 */
change_training_options(TrainingSite & trainingsite,TrainingAttribute attr,int32_t const val)1017 void Player::change_training_options(TrainingSite& trainingsite,
1018 TrainingAttribute attr,
1019 int32_t const val) {
1020 if (trainingsite.get_owner() == this) {
1021 trainingsite.set_pri(attr, trainingsite.get_pri(attr) + val);
1022 }
1023 }
1024
1025 /*
1026 ===========
1027 Forces the drop of given soldier at given house
1028 ===========
1029 */
drop_soldier(PlayerImmovable & imm,Soldier & soldier)1030 void Player::drop_soldier(PlayerImmovable& imm, Soldier& soldier) {
1031 if (imm.get_owner() != this)
1032 return;
1033 if (soldier.descr().type() != MapObjectType::SOLDIER)
1034 return;
1035 if (upcast(Building, building, &imm)) {
1036 SoldierControl* soldier_control = building->mutable_soldier_control();
1037 if (soldier_control != nullptr) {
1038 soldier_control->drop_soldier(soldier);
1039 }
1040 }
1041 }
1042
1043 /*
1044 ===========
1045 ===========
1046 */
1047
1048 /**
1049 * Get a list of soldiers that this player can use to attack the
1050 * building at the given flag.
1051 *
1052 * The default attack should just take the first N soldiers of the
1053 * returned array.
1054 */
1055 // TODO(unknown): Perform a meaningful sort on the soldiers array.
1056 uint32_t
find_attack_soldiers(Flag & flag,std::vector<Soldier * > * soldiers,uint32_t nr_wanted)1057 Player::find_attack_soldiers(Flag& flag, std::vector<Soldier*>* soldiers, uint32_t nr_wanted) {
1058 uint32_t count = 0;
1059
1060 if (soldiers)
1061 soldiers->clear();
1062
1063 const Map& map = egbase().map();
1064 std::vector<BaseImmovable*> flags;
1065
1066 map.find_reachable_immovables_unique(
1067 egbase(), Area<FCoords>(map.get_fcoords(flag.get_position()), 25), flags,
1068 CheckStepDefault(MOVECAPS_WALK), FindFlagOf(FindImmovablePlayerMilitarySite(*this)));
1069
1070 if (flags.empty())
1071 return 0;
1072
1073 for (BaseImmovable* temp_flag : flags) {
1074 upcast(Flag, attackerflag, temp_flag);
1075 const SoldierControl* soldier_control = attackerflag->get_building()->soldier_control();
1076 assert(soldier_control != nullptr);
1077 std::vector<Soldier*> const present = soldier_control->present_soldiers();
1078 uint32_t const nr_staying = soldier_control->min_soldier_capacity();
1079 uint32_t const nr_present = present.size();
1080 if (nr_staying < nr_present) {
1081 uint32_t const nr_taken = std::min(nr_wanted, nr_present - nr_staying);
1082 if (soldiers)
1083 soldiers->insert(soldiers->end(), present.begin(), present.begin() + nr_taken);
1084 count += nr_taken;
1085 nr_wanted -= nr_taken;
1086 if (!nr_wanted)
1087 break;
1088 }
1089 }
1090
1091 return count;
1092 }
1093
1094 // TODO(unknown): Clean this mess up. The only action we really have right now is
1095 // to attack, so pretending we have more types is pointless.
enemyflagaction(Flag & flag,PlayerNumber const attacker,const std::vector<Widelands::Soldier * > & soldiers)1096 void Player::enemyflagaction(Flag& flag,
1097 PlayerNumber const attacker,
1098 const std::vector<Widelands::Soldier*>& soldiers) {
1099 if (attacker != player_number()) {
1100 log("Player (%d) is not the sender of an attack (%d)\n", attacker, player_number());
1101 } else if (soldiers.empty()) {
1102 log("enemyflagaction: no soldiers given\n");
1103 } else if (is_hostile(flag.owner())) {
1104 if (Building* const building = flag.get_building()) {
1105 if (const AttackTarget* attack_target = building->attack_target()) {
1106 if (attack_target->can_be_attacked()) {
1107 for (Soldier* temp_attacker : soldiers) {
1108 assert(temp_attacker);
1109 assert(temp_attacker->get_owner() == this);
1110 if (upcast(MilitarySite, ms, temp_attacker->get_location(egbase()))) {
1111 assert(ms->get_owner() == this);
1112 ms->send_attacker(*temp_attacker, *building);
1113 } else {
1114 // The soldier may not be in a militarysite anymore if he was kicked out
1115 // in the short delay between sending and executing a playercommand
1116 log("Player(%u)::enemyflagaction: Not sending soldier %u because he left the "
1117 "building\n",
1118 player_number(), temp_attacker->serial());
1119 }
1120 }
1121 }
1122 }
1123 }
1124 }
1125 }
1126
rediscover_node(const Map & map,const FCoords & f)1127 void Player::rediscover_node(const Map& map, const FCoords& f) {
1128
1129 assert(0 <= f.x);
1130 assert(f.x < map.get_width());
1131 assert(0 <= f.y);
1132 assert(f.y < map.get_height());
1133 const Widelands::Field& first_map_field = map[0];
1134 assert(&first_map_field <= f.field);
1135 assert(f.field < &first_map_field + map.max_index());
1136
1137 Field& field = fields_[f.field - &first_map_field];
1138
1139 assert(fields_.get() <= &field);
1140 assert(&field < fields_.get() + map.max_index());
1141
1142 { // discover everything (above the ground) in this field
1143 field.terrains = f.field->get_terrains();
1144 field.r_e = f.field->get_road(WALK_E);
1145 field.r_se = f.field->get_road(WALK_SE);
1146 field.r_sw = f.field->get_road(WALK_SW);
1147 field.owner = f.field->get_owned_by();
1148
1149 // Check if this node is part of a border
1150 int32_t const mapwidth = map.get_width();
1151 // right neighbour
1152 FCoords r = map.r_n(f);
1153 PlayerNumber r_owner_number = r.field->get_owned_by();
1154 MapIndex r_index = map.get_index(r, mapwidth);
1155 Vision r_vision = vision(r_index);
1156 // top right neighbour
1157 FCoords tr = map.tr_n(f);
1158 PlayerNumber tr_owner_number = tr.field->get_owned_by();
1159 // bottom right neighbour
1160 FCoords br = map.br_n(f);
1161 PlayerNumber br_owner_number = br.field->get_owned_by();
1162 MapIndex br_index = map.get_index(br, mapwidth);
1163 Vision br_vision = vision(br_index);
1164 // bottom left neighbour
1165 FCoords bl = map.bl_n(f);
1166 PlayerNumber bl_owner_number = bl.field->get_owned_by();
1167 MapIndex bl_index = map.get_index(bl, mapwidth);
1168 Vision bl_vision = vision(bl_index);
1169 // left neighbour
1170 FCoords l = map.l_n(f);
1171 PlayerNumber l_owner_number = l.field->get_owned_by();
1172
1173 field.border = f.field->is_border();
1174 field.border_r = ((1 | r_vision) && (r_owner_number == field.owner) &&
1175 ((tr_owner_number == field.owner) ^ (br_owner_number == field.owner)));
1176 field.border_br = ((1 | bl_vision) && (bl_owner_number == field.owner) &&
1177 ((l_owner_number == field.owner) ^ (br_owner_number == field.owner)));
1178 field.border_bl = ((1 | br_vision) && (br_owner_number == field.owner) &&
1179 ((r_owner_number == field.owner) ^ (bl_owner_number == field.owner)));
1180
1181 {
1182 const MapObjectDescr* map_object_descr;
1183 field.constructionsite.becomes = nullptr;
1184 if (const BaseImmovable* base_immovable = f.field->get_immovable()) {
1185 map_object_descr = &base_immovable->descr();
1186
1187 if (Road::is_road_descr(map_object_descr) ||
1188 Waterway::is_waterway_descr(map_object_descr))
1189 map_object_descr = nullptr;
1190 else if (upcast(Building const, building, base_immovable)) {
1191 if (building->get_position() != f)
1192 // This is not the building's main position so we can not see it.
1193 map_object_descr = nullptr;
1194 else {
1195 if (upcast(ConstructionSite const, cs, building)) {
1196 field.constructionsite = const_cast<ConstructionSite*>(cs)->get_info();
1197 }
1198 }
1199 }
1200 } else
1201 map_object_descr = nullptr;
1202 field.map_object_descr = map_object_descr;
1203 }
1204 }
1205 { // discover the D triangle and the SW edge of the top right neighbour
1206 FCoords tr = map.tr_n(f);
1207 Field& tr_field = fields_[tr.field - &first_map_field];
1208 if (tr_field.vision <= 1) {
1209 tr_field.terrains.d = tr.field->terrain_d();
1210 tr_field.r_sw = tr.field->get_road(WALK_SW);
1211 tr_field.owner = tr.field->get_owned_by();
1212 }
1213 }
1214 { // discover both triangles and the SE edge of the top left neighbour
1215 FCoords tl = map.tl_n(f);
1216 Field& tl_field = fields_[tl.field - &first_map_field];
1217 if (tl_field.vision <= 1) {
1218 tl_field.terrains = tl.field->get_terrains();
1219 tl_field.r_se = tl.field->get_road(WALK_SE);
1220 tl_field.owner = tl.field->get_owned_by();
1221 }
1222 }
1223 { // discover the R triangle and the E edge of the left neighbour
1224 FCoords l = map.l_n(f);
1225 Field& l_field = fields_[l.field - &first_map_field];
1226 if (l_field.vision <= 1) {
1227 l_field.terrains.r = l.field->terrain_r();
1228 l_field.r_e = l.field->get_road(WALK_E);
1229 l_field.owner = l.field->get_owned_by();
1230 }
1231 }
1232 }
1233
1234 /// Returns the resulting vision.
see_node(const Map & map,const FCoords & f,Time const gametime,bool const forward)1235 Vision Player::see_node(const Map& map, const FCoords& f, Time const gametime, bool const forward) {
1236 assert(0 <= f.x);
1237 assert(f.x < map.get_width());
1238 assert(0 <= f.y);
1239 assert(f.y < map.get_height());
1240 const Widelands::Field& first_map_field = map[0];
1241 assert(&first_map_field <= f.field);
1242 assert(f.field < &first_map_field + map.max_index());
1243
1244 // If this is not already a forwarded call, we should inform allied players
1245 // as well of this change.
1246 if (!team_player_uptodate_)
1247 update_team_players();
1248 if (!forward && !team_player_.empty()) {
1249 for (uint8_t j = 0; j < team_player_.size(); ++j)
1250 team_player_[j]->see_node(map, f, gametime, true);
1251 }
1252
1253 Field& field = fields_[f.field - &first_map_field];
1254 assert(fields_.get() <= &field);
1255 assert(&field < fields_.get() + map.max_index());
1256
1257 if (field.vision == 0) {
1258 field.vision = 1;
1259 }
1260 if (field.vision == 1) {
1261 rediscover_node(map, f);
1262 }
1263 return ++field.vision;
1264 }
1265
1266 /// If 'mode' = UnseeMode::kUnexplore, fields will be marked as unexplored. Else, player no longer
1267 /// sees what's currently going on. Returns the vision that this node had before it was hidden.
unsee_node(MapIndex const i,Time const gametime,const SeeUnseeNode mode,bool const forward)1268 Vision Player::unsee_node(MapIndex const i,
1269 Time const gametime,
1270 const SeeUnseeNode mode,
1271 bool const forward) {
1272 Field& field = fields_[i];
1273 if ((mode == SeeUnseeNode::kUnsee && field.vision <= 1) ||
1274 field.vision < 1) // Already does not see this
1275 return field.vision;
1276
1277 const Vision original_vision = field.vision;
1278
1279 // If this is not already a forwarded call, we should inform allied players
1280 // as well of this change.
1281 if (!team_player_uptodate_)
1282 update_team_players();
1283 if (!forward && !team_player_.empty()) {
1284 for (uint8_t j = 0; j < team_player_.size(); ++j)
1285 team_player_[j]->unsee_node(i, gametime, mode, true);
1286 }
1287
1288 if (mode == SeeUnseeNode::kUnexplore) {
1289 field.vision = 0;
1290 } else {
1291 --field.vision;
1292 assert(1 <= field.vision);
1293 }
1294 if (field.vision < 2) {
1295 field.time_node_last_unseen = gametime;
1296 }
1297 return original_vision;
1298 }
1299
hide_or_reveal_field(const uint32_t gametime,const Coords & coords,SeeUnseeNode mode)1300 void Player::hide_or_reveal_field(const uint32_t gametime,
1301 const Coords& coords,
1302 SeeUnseeNode mode) {
1303 const Map& map = egbase().map();
1304 FCoords fcoords = map.get_fcoords(coords);
1305 const Widelands::MapIndex index = fcoords.field - &map[0];
1306
1307 switch (mode) {
1308 // Reveal field
1309 case SeeUnseeNode::kReveal: {
1310 Widelands::Vision new_vision = see_node(map, fcoords, gametime);
1311 // If the field was manually hidden, restore the original vision
1312 if (hidden_fields_.count(index) == 1) {
1313 auto iter = hidden_fields_.find(index);
1314 Vision original_vision = iter->second;
1315 while (new_vision < original_vision) {
1316 new_vision = see_node(map, fcoords, gametime);
1317 }
1318 hidden_fields_.erase(iter);
1319 }
1320 } break;
1321 // Hide field
1322 case SeeUnseeNode::kUnsee:
1323 case SeeUnseeNode::kUnexplore: {
1324 const Widelands::Vision new_vision = unsee_node(index, gametime, mode);
1325 // Remember the original vision so that we can unhide the fields again
1326 if (hidden_fields_.count(index) != 1) {
1327 hidden_fields_.insert(std::make_pair(index, new_vision));
1328 }
1329 } break;
1330 }
1331 }
1332
1333 /**
1334 * Called by Game::think to sample statistics data in regular intervals.
1335 */
sample_statistics()1336 void Player::sample_statistics() {
1337 assert(ware_productions_.size() == egbase().tribes().nrwares());
1338 assert(ware_consumptions_.size() == egbase().tribes().nrwares());
1339 assert(ware_stocks_.size() == egbase().tribes().nrwares());
1340
1341 // Calculate stocks
1342 std::vector<uint32_t> stocks(egbase().tribes().nrwares());
1343
1344 for (const auto& economy : economies()) {
1345 if (economy.second->type() == wwWARE) {
1346 for (Widelands::Warehouse* warehouse : economy.second->warehouses()) {
1347 const Widelands::WareList& wares = warehouse->get_wares();
1348 for (size_t id = 0; id < stocks.size(); ++id) {
1349 stocks[id] += wares.stock(DescriptionIndex(id));
1350 }
1351 }
1352 }
1353 }
1354
1355 // Update statistics
1356 for (uint32_t i = 0; i < ware_productions_.size(); ++i) {
1357 ware_productions_[i].push_back(current_produced_statistics_[i]);
1358 current_produced_statistics_[i] = 0;
1359
1360 ware_consumptions_[i].push_back(current_consumed_statistics_[i]);
1361 current_consumed_statistics_[i] = 0;
1362
1363 ware_stocks_[i].push_back(stocks[i]);
1364 }
1365 }
1366
1367 /**
1368 * A ware was produced. Update the corresponding statistics.
1369 */
ware_produced(DescriptionIndex const wareid)1370 void Player::ware_produced(DescriptionIndex const wareid) {
1371 assert(ware_productions_.size() == egbase().tribes().nrwares());
1372 assert(egbase().tribes().ware_exists(wareid));
1373 ++current_produced_statistics_[wareid];
1374 }
1375
1376 /**
1377 * Return count of produced wares for ware index
1378 */
get_current_produced_statistics(uint8_t const wareid)1379 uint32_t Player::get_current_produced_statistics(uint8_t const wareid) {
1380 assert(wareid < egbase().tribes().nrwares());
1381 assert(wareid < ware_productions_.size());
1382 assert(wareid < current_produced_statistics_.size());
1383 uint32_t sum = current_produced_statistics_[wareid];
1384 for (const auto stat : *get_ware_production_statistics(wareid)) {
1385 sum += stat;
1386 }
1387 return sum;
1388 }
1389
1390 /**
1391 * Some units from one kind of ware were consumed.
1392 * Update the corresponding statistics
1393 *
1394 * \param wareid the ID of the consumed wares
1395 * \param count the number of consumed wares
1396 */
ware_consumed(DescriptionIndex const wareid,uint8_t const count)1397 void Player::ware_consumed(DescriptionIndex const wareid, uint8_t const count) {
1398 assert(ware_consumptions_.size() == egbase().tribes().nrwares());
1399 assert(egbase().tribes().ware_exists(wareid));
1400
1401 current_consumed_statistics_[wareid] += count;
1402 }
1403
1404 /**
1405 * Get current ware production statistics
1406 */
1407 const std::vector<uint32_t>*
get_ware_production_statistics(DescriptionIndex const ware) const1408 Player::get_ware_production_statistics(DescriptionIndex const ware) const {
1409 assert(ware < static_cast<int>(ware_productions_.size()));
1410 return &ware_productions_[ware];
1411 }
1412
1413 /**
1414 * Get current ware consumption statistics
1415 */
1416 const std::vector<uint32_t>*
get_ware_consumption_statistics(DescriptionIndex const ware) const1417 Player::get_ware_consumption_statistics(DescriptionIndex const ware) const {
1418
1419 assert(ware < static_cast<int>(ware_consumptions_.size()));
1420
1421 return &ware_consumptions_[ware];
1422 }
1423
get_ware_stock_statistics(DescriptionIndex const ware) const1424 const std::vector<uint32_t>* Player::get_ware_stock_statistics(DescriptionIndex const ware) const {
1425 assert(ware < static_cast<int>(ware_stocks_.size()));
1426
1427 return &ware_stocks_[ware];
1428 }
1429
1430 const Player::BuildingStatsVector&
get_building_statistics(const DescriptionIndex & i) const1431 Player::get_building_statistics(const DescriptionIndex& i) const {
1432 return *const_cast<Player*>(this)->get_mutable_building_statistics(i);
1433 }
1434
get_mutable_building_statistics(const DescriptionIndex & i)1435 Player::BuildingStatsVector* Player::get_mutable_building_statistics(const DescriptionIndex& i) {
1436 DescriptionIndex const nr_buildings = egbase().tribes().nrbuildings();
1437 if (building_stats_.size() < nr_buildings)
1438 building_stats_.resize(nr_buildings);
1439 return &building_stats_[i];
1440 }
1441
1442 /**
1443 * Add or remove the given building from building statistics.
1444 * Only to be called by \ref receive
1445 */
update_building_statistics(Building & building,NoteImmovable::Ownership ownership)1446 void Player::update_building_statistics(Building& building, NoteImmovable::Ownership ownership) {
1447 upcast(ConstructionSite const, constructionsite, &building);
1448 const std::string& building_name =
1449 constructionsite ? constructionsite->building().name() : building.descr().name();
1450
1451 const size_t nr_buildings = egbase().tribes().nrbuildings();
1452
1453 // Get the valid vector for this
1454 if (building_stats_.size() < nr_buildings)
1455 building_stats_.resize(nr_buildings);
1456
1457 std::vector<BuildingStats>& stat =
1458 *get_mutable_building_statistics(egbase().tribes().building_index(building_name.c_str()));
1459
1460 if (ownership == NoteImmovable::Ownership::GAINED) {
1461 BuildingStats new_building;
1462 new_building.is_constructionsite = constructionsite;
1463 new_building.pos = building.get_position();
1464 stat.push_back(new_building);
1465 } else {
1466 Coords const building_position = building.get_position();
1467 for (uint32_t i = 0; i < stat.size(); ++i) {
1468 if (stat[i].pos == building_position) {
1469 stat.erase(stat.begin() + i);
1470 return;
1471 }
1472 }
1473
1474 throw wexception("InteractivePlayer::loose_immovable(): A building should be "
1475 "removed at (%i, %i), but nothing is known about this building!",
1476 building_position.x, building_position.y);
1477 }
1478 }
1479 /**
1480 * Functions used by AI to save/read AI data stored in Player class.
1481 */
1482
set_ai(const std::string & ai)1483 void Player::set_ai(const std::string& ai) {
1484 ai_ = ai;
1485 }
1486
get_ai() const1487 const std::string& Player::get_ai() const {
1488 return ai_;
1489 }
1490
is_attack_forbidden(PlayerNumber who) const1491 bool Player::is_attack_forbidden(PlayerNumber who) const {
1492 return forbid_attack_.find(who) != forbid_attack_.end();
1493 }
1494
set_attack_forbidden(PlayerNumber who,bool forbid)1495 void Player::set_attack_forbidden(PlayerNumber who, bool forbid) {
1496 const auto it = forbid_attack_.find(who);
1497 if (forbid ^ (it == forbid_attack_.end())) {
1498 return;
1499 } else if (forbid) {
1500 forbid_attack_.emplace(who);
1501 } else {
1502 forbid_attack_.erase(it);
1503 }
1504 }
1505
1506 /**
1507 * Pick random name from remaining names (if any)
1508 */
pick_shipname()1509 const std::string Player::pick_shipname() {
1510 ++ship_name_counter_;
1511
1512 if (!remaining_shipnames_.empty()) {
1513 Game& game = dynamic_cast<Game&>(egbase());
1514 assert(is_a(Game, &egbase()));
1515 const uint32_t index = game.logic_rand() % remaining_shipnames_.size();
1516 std::unordered_set<std::string>::iterator it = remaining_shipnames_.begin();
1517 std::advance(it, index);
1518 std::string new_name = *it;
1519 remaining_shipnames_.erase(it);
1520 return new_name;
1521 }
1522 return (boost::format(pgettext("shipname", "Ship %d")) % ship_name_counter_).str();
1523 }
1524
1525 /**
1526 * Read remaining ship indexes to the give file
1527 *
1528 * \param fr source stream
1529 */
read_remaining_shipnames(FileRead & fr)1530 void Player::read_remaining_shipnames(FileRead& fr) {
1531 // First get rid of default shipnames
1532 remaining_shipnames_.clear();
1533 const uint16_t count = fr.unsigned_16();
1534 for (uint16_t i = 0; i < count; ++i) {
1535 remaining_shipnames_.insert(fr.string());
1536 }
1537 ship_name_counter_ = fr.unsigned_32();
1538 }
1539
1540 /**
1541 * Read statistics data from a file.
1542 *
1543 * \param fr source stream
1544 */
read_statistics(FileRead & fr,const uint16_t packet_version,const TribesLegacyLookupTable & lookup_table)1545 void Player::read_statistics(FileRead& fr,
1546 const uint16_t packet_version,
1547 const TribesLegacyLookupTable& lookup_table) {
1548 uint16_t nr_wares = fr.unsigned_16();
1549 size_t nr_entries = fr.unsigned_16();
1550
1551 // Stats are saved as a single string to reduce number of hard disk write operations
1552 const auto parse_stats = [nr_entries](
1553 std::vector<std::vector<uint32_t>>* stats, const DescriptionIndex ware_index,
1554 const std::string& stats_string, const std::string& description) {
1555 if (!stats_string.empty()) {
1556 std::vector<std::string> stats_vector;
1557 boost::split(stats_vector, stats_string, boost::is_any_of("|"));
1558 if (stats_vector.size() != nr_entries) {
1559 throw GameDataError("wrong number of %s statistics - expected %" PRIuS
1560 " but got %" PRIuS,
1561 description.c_str(), nr_entries, stats_vector.size());
1562 }
1563 for (size_t j = 0; j < nr_entries; ++j) {
1564 stats->at(ware_index)[j] = static_cast<unsigned int>(atoi(stats_vector.at(j).c_str()));
1565 }
1566 } else if (nr_entries > 0) {
1567 throw GameDataError("wrong number of %s statistics - expected %" PRIuS " but got 0",
1568 description.c_str(), nr_entries);
1569 }
1570 };
1571
1572 for (uint32_t i = 0; i < current_produced_statistics_.size(); ++i)
1573 ware_productions_[i].resize(nr_entries);
1574
1575 for (uint16_t i = 0; i < nr_wares; ++i) {
1576 const std::string name = lookup_table.lookup_ware(fr.c_string());
1577 const DescriptionIndex idx = egbase().tribes().ware_index(name);
1578 if (!egbase().tribes().ware_exists(idx)) {
1579 log("Player %u statistics: unknown ware name %s", player_number(), name.c_str());
1580 continue;
1581 }
1582
1583 current_produced_statistics_[idx] = fr.unsigned_32();
1584 if (packet_version < 22) {
1585 for (uint32_t j = 0; j < nr_entries; ++j) {
1586 ware_productions_[idx][j] = fr.unsigned_32();
1587 }
1588 } else {
1589 parse_stats(&ware_productions_, idx, fr.c_string(), "produced");
1590 }
1591 }
1592
1593 // Read consumption statistics
1594 nr_wares = fr.unsigned_16();
1595 nr_entries = fr.unsigned_16();
1596
1597 for (uint32_t i = 0; i < current_consumed_statistics_.size(); ++i)
1598 ware_consumptions_[i].resize(nr_entries);
1599
1600 for (uint16_t i = 0; i < nr_wares; ++i) {
1601 const std::string name = lookup_table.lookup_ware(fr.c_string());
1602 const DescriptionIndex idx = egbase().tribes().ware_index(name);
1603 if (!egbase().tribes().ware_exists(idx)) {
1604 log("Player %u consumption statistics: unknown ware name %s", player_number(),
1605 name.c_str());
1606 continue;
1607 }
1608
1609 current_consumed_statistics_[idx] = fr.unsigned_32();
1610 // TODO(GunChleoc): Get rid of this savegame compatibility code after build 21
1611 if (packet_version < 22) {
1612 for (uint32_t j = 0; j < nr_entries; ++j) {
1613 ware_consumptions_[idx][j] = fr.unsigned_32();
1614 }
1615 } else {
1616 parse_stats(&ware_consumptions_, idx, fr.c_string(), "consumed");
1617 }
1618 }
1619
1620 // Read stock statistics
1621 nr_wares = fr.unsigned_16();
1622 nr_entries = fr.unsigned_16();
1623
1624 for (uint32_t i = 0; i < ware_stocks_.size(); ++i)
1625 ware_stocks_[i].resize(nr_entries);
1626
1627 for (uint16_t i = 0; i < nr_wares; ++i) {
1628 const std::string name = lookup_table.lookup_ware(fr.c_string());
1629 const DescriptionIndex idx = egbase().tribes().ware_index(name);
1630 if (!egbase().tribes().ware_exists(idx)) {
1631 log("Player %u stock statistics: unknown ware name %s", player_number(), name.c_str());
1632 continue;
1633 }
1634
1635 if (packet_version < 22) {
1636 for (uint32_t j = 0; j < nr_entries; ++j) {
1637 ware_stocks_[idx][j] = fr.unsigned_32();
1638 }
1639 } else {
1640 parse_stats(&ware_stocks_, idx, fr.c_string(), "stock");
1641 }
1642 }
1643
1644 // All statistics should have the same size
1645 assert(ware_productions_.size() == ware_consumptions_.size());
1646 assert(ware_productions_[0].size() == ware_consumptions_[0].size());
1647
1648 assert(ware_productions_.size() == ware_stocks_.size());
1649 assert(ware_productions_[0].size() == ware_stocks_[0].size());
1650 }
1651
1652 /**
1653 * Write remaining ship indexes to the given file
1654 */
write_remaining_shipnames(FileWrite & fw) const1655 void Player::write_remaining_shipnames(FileWrite& fw) const {
1656 fw.unsigned_16(remaining_shipnames_.size());
1657 for (const auto& shipname : remaining_shipnames_) {
1658 fw.string(shipname);
1659 }
1660 fw.unsigned_32(ship_name_counter_);
1661 }
1662
1663 /**
1664 * Write statistics data to the given file
1665 */
write_statistics(FileWrite & fw) const1666 void Player::write_statistics(FileWrite& fw) const {
1667 // Save stats as a single string to reduce number of hard disk write operations
1668 const auto write_stats = [&fw](
1669 const std::vector<std::vector<uint32_t>>& stats, const DescriptionIndex ware_index) {
1670 std::ostringstream oss("");
1671 const int sizem = stats[ware_index].size() - 1;
1672 if (sizem >= 0) {
1673 for (int i = 0; i < sizem; ++i) {
1674 oss << stats[ware_index][i] << "|";
1675 }
1676 oss << stats[ware_index][sizem];
1677 }
1678 fw.c_string(oss.str());
1679 };
1680
1681 const Tribes& tribes = egbase().tribes();
1682 const std::set<DescriptionIndex>& tribe_wares = tribe().wares();
1683 const size_t nr_wares = tribe_wares.size();
1684
1685 // Write produce statistics
1686 fw.unsigned_16(nr_wares);
1687 fw.unsigned_16(ware_productions_[0].size());
1688
1689 for (const DescriptionIndex ware_index : tribe_wares) {
1690 fw.c_string(tribes.get_ware_descr(ware_index)->name());
1691 fw.unsigned_32(current_produced_statistics_[ware_index]);
1692 write_stats(ware_productions_, ware_index);
1693 }
1694
1695 // Write consume statistics
1696 fw.unsigned_16(nr_wares);
1697 fw.unsigned_16(ware_consumptions_[0].size());
1698
1699 for (const DescriptionIndex ware_index : tribe_wares) {
1700 fw.c_string(tribes.get_ware_descr(ware_index)->name());
1701 fw.unsigned_32(current_consumed_statistics_[ware_index]);
1702 write_stats(ware_consumptions_, ware_index);
1703 }
1704
1705 // Write stock statistics
1706 fw.unsigned_16(nr_wares);
1707 fw.unsigned_16(ware_stocks_[0].size());
1708
1709 for (const DescriptionIndex ware_index : tribe_wares) {
1710 fw.c_string(tribes.get_ware_descr(ware_index)->name());
1711 write_stats(ware_stocks_, ware_index);
1712 }
1713 }
1714 } // namespace Widelands
1715