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/map_objects/map_object.h"
21
22 #include <algorithm>
23 #include <cstdarg>
24 #include <memory>
25
26 #include "base/log.h"
27 #include "base/wexception.h"
28 #include "graphic/animation/animation_manager.h"
29 #include "graphic/font_handler.h"
30 #include "graphic/graphic.h"
31 #include "graphic/rendertarget.h"
32 #include "graphic/style_manager.h"
33 #include "graphic/text_layout.h"
34 #include "io/fileread.h"
35 #include "io/filewrite.h"
36 #include "logic/cmd_queue.h"
37 #include "logic/game.h"
38 #include "logic/game_data_error.h"
39 #include "logic/player.h"
40 #include "map_io/map_object_loader.h"
41 #include "map_io/map_object_saver.h"
42
43 namespace {
44 char const* const animation_direction_names[6] = {"_ne", "_e", "_se", "_sw", "_w", "_nw"};
45 } // namespace
46
47 namespace Widelands {
48
CmdDestroyMapObject(uint32_t const t,MapObject & o)49 CmdDestroyMapObject::CmdDestroyMapObject(uint32_t const t, MapObject& o)
50 : GameLogicCommand(t), obj_serial(o.serial()) {
51 }
52
execute(Game & game)53 void CmdDestroyMapObject::execute(Game& game) {
54 game.syncstream().unsigned_8(SyncEntry::kDestroyObject);
55 game.syncstream().unsigned_32(obj_serial);
56
57 if (MapObject* obj = game.objects().get_object(obj_serial))
58 obj->destroy(game);
59 }
60
61 constexpr uint16_t kCurrentPacketVersionDestroyMapObject = 1;
62
read(FileRead & fr,EditorGameBase & egbase,MapObjectLoader & mol)63 void CmdDestroyMapObject::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {
64 try {
65 uint16_t const packet_version = fr.unsigned_16();
66 if (packet_version == kCurrentPacketVersionDestroyMapObject) {
67 GameLogicCommand::read(fr, egbase, mol);
68 if (Serial const serial = fr.unsigned_32())
69 try {
70 obj_serial = mol.get<MapObject>(serial).serial();
71 } catch (const WException& e) {
72 throw GameDataError("%u: %s", serial, e.what());
73 }
74 else
75 obj_serial = 0;
76 } else {
77 throw UnhandledVersionError(
78 "CmdDestroyMapObject", packet_version, kCurrentPacketVersionDestroyMapObject);
79 }
80 } catch (const WException& e) {
81 throw GameDataError("destroy map object: %s", e.what());
82 }
83 }
write(FileWrite & fw,EditorGameBase & egbase,MapObjectSaver & mos)84 void CmdDestroyMapObject::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) {
85 // First, write version
86 fw.unsigned_16(kCurrentPacketVersionDestroyMapObject);
87
88 // Write base classes
89 GameLogicCommand::write(fw, egbase, mos);
90
91 // Now serial
92 fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(obj_serial)));
93 }
94
CmdAct(uint32_t const t,MapObject & o,int32_t const a)95 CmdAct::CmdAct(uint32_t const t, MapObject& o, int32_t const a)
96 : GameLogicCommand(t), obj_serial(o.serial()), arg(a) {
97 }
98
execute(Game & game)99 void CmdAct::execute(Game& game) {
100 game.syncstream().unsigned_8(SyncEntry::kCmdAct);
101 game.syncstream().unsigned_32(obj_serial);
102
103 if (MapObject* const obj = game.objects().get_object(obj_serial)) {
104 game.syncstream().unsigned_8(static_cast<uint8_t>(obj->descr().type()));
105 obj->act(game, arg);
106 } else {
107 game.syncstream().unsigned_8(static_cast<uint8_t>(MapObjectType::MAPOBJECT));
108 }
109 // the object must queue the next CMD_ACT itself if necessary
110 }
111
112 constexpr uint16_t kCurrentPacketVersionCmdAct = 1;
113
read(FileRead & fr,EditorGameBase & egbase,MapObjectLoader & mol)114 void CmdAct::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {
115 try {
116 uint16_t const packet_version = fr.unsigned_16();
117 if (packet_version == kCurrentPacketVersionCmdAct) {
118 GameLogicCommand::read(fr, egbase, mol);
119 if (Serial const object_serial = fr.unsigned_32())
120 try {
121 obj_serial = mol.get<MapObject>(object_serial).serial();
122 } catch (const WException& e) {
123 throw GameDataError("object %u: %s", object_serial, e.what());
124 }
125 else
126 obj_serial = 0;
127 arg = fr.unsigned_32();
128 } else {
129 throw UnhandledVersionError("CmdAct", packet_version, kCurrentPacketVersionCmdAct);
130 }
131 } catch (const WException& e) {
132 throw wexception("act: %s", e.what());
133 }
134 }
write(FileWrite & fw,EditorGameBase & egbase,MapObjectSaver & mos)135 void CmdAct::write(FileWrite& fw, EditorGameBase& egbase, MapObjectSaver& mos) {
136 // First, write version
137 fw.unsigned_16(kCurrentPacketVersionCmdAct);
138
139 // Write base classes
140 GameLogicCommand::write(fw, egbase, mos);
141
142 // Now serial
143 fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(obj_serial)));
144
145 // And arg
146 fw.unsigned_32(arg);
147 }
148
~ObjectManager()149 ObjectManager::~ObjectManager() {
150 // better not throw an exception in a destructor...
151 if (!objects_.empty())
152 log("ObjectManager: ouch! remaining objects\n");
153
154 log("lastserial: %i\n", lastserial_);
155 }
156
157 /**
158 * Clear all objects
159 */
cleanup(EditorGameBase & egbase)160 void ObjectManager::cleanup(EditorGameBase& egbase) {
161 // If all wares (read: flags) of an economy are gone, but some workers remain,
162 // the economy is destroyed before workers detach. This can cause segfault.
163 // Destruction happens in correct order after this dirty quickie.
164 // Run at the end of game, algorithmic efficiency may be what it is.
165 const static std::vector<MapObjectType> killusfirst{
166 MapObjectType::WATERWAY, MapObjectType::FERRY, MapObjectType::FERRY_FLEET,
167 MapObjectType::SHIP, MapObjectType::SHIP_FLEET, MapObjectType::PORTDOCK,
168 MapObjectType::WORKER};
169 for (auto moi : killusfirst) {
170 while (!objects_.empty()) {
171 MapObjectMap::iterator it = objects_.begin();
172 while (it != objects_.end() && (moi) != it->second->descr_->type())
173 it++;
174 if (it == objects_.end()) {
175 break;
176 } else {
177 it->second->remove(egbase);
178 }
179 }
180 }
181 while (!objects_.empty()) {
182 MapObjectMap::iterator it = objects_.begin();
183 it->second->remove(egbase);
184 }
185
186 lastserial_ = 0;
187 }
188
189 /**
190 * Insert the given MapObject into the object manager
191 */
insert(MapObject * obj)192 void ObjectManager::insert(MapObject* obj) {
193 ++lastserial_;
194 assert(lastserial_);
195 obj->serial_ = lastserial_;
196 objects_[lastserial_] = obj;
197 }
198
199 /**
200 * Remove the MapObject from the manager
201 */
remove(MapObject & obj)202 void ObjectManager::remove(MapObject& obj) {
203 objects_.erase(obj.serial_);
204 }
205
206 /*
207 * Return the list of all serials currently in use
208 */
all_object_serials_ordered() const209 std::vector<Serial> ObjectManager::all_object_serials_ordered() const {
210 std::vector<Serial> rv;
211
212 for (const auto& o : objects_) {
213 rv.push_back(o.first);
214 }
215
216 std::sort(rv.begin(), rv.end());
217
218 return rv;
219 }
220
get(const EditorGameBase & egbase)221 MapObject* ObjectPointer::get(const EditorGameBase& egbase) {
222 if (!serial_)
223 return nullptr;
224 MapObject* const obj = egbase.objects().get_object(serial_);
225 if (!obj)
226 serial_ = 0;
227 return obj;
228 }
229
230 // This version also returns a pointer to a non-const object,
231 // because it is logically the pointer that is const, not the object
232 // that is pointed to.
233 // That is, a 'const ObjectPointer' behaves like a 'ObjectPointer * const'.
get(const EditorGameBase & egbase) const234 MapObject* ObjectPointer::get(const EditorGameBase& egbase) const {
235 return serial_ ? egbase.objects().get_object(serial_) : nullptr;
236 }
237
238 /*
239 ==============================================================================
240
241 MapObjectDescr IMPLEMENTATION
242
243 ==============================================================================
244 */
MapObjectDescr(const MapObjectType init_type,const std::string & init_name,const std::string & init_descname,const std::string & init_helptext_script)245 MapObjectDescr::MapObjectDescr(const MapObjectType init_type,
246 const std::string& init_name,
247 const std::string& init_descname,
248 const std::string& init_helptext_script)
249 : type_(init_type),
250 name_(init_name),
251 descname_(init_descname),
252 helptext_script_(init_helptext_script) {
253 }
MapObjectDescr(const MapObjectType init_type,const std::string & init_name,const std::string & init_descname,const LuaTable & table)254 MapObjectDescr::MapObjectDescr(const MapObjectType init_type,
255 const std::string& init_name,
256 const std::string& init_descname,
257 const LuaTable& table)
258 : MapObjectDescr(init_type,
259 init_name,
260 init_descname,
261 table.has_key("helptext_script") ? table.get_string("helptext_script") : "") {
262 bool has_animations = false;
263 // TODO(GunChleoc): When all animations have been converted, require that animation_directory is
264 // not empty if the map object has animations.
265 const std::string animation_directory(
266 table.has_key("animation_directory") ? table.get_string("animation_directory") : "");
267 if (table.has_key("animations")) {
268 has_animations = true;
269 add_animations(*table.get_table("animations"), animation_directory, Animation::Type::kFiles);
270 }
271 if (table.has_key("spritesheets")) {
272 has_animations = true;
273 add_animations(
274 *table.get_table("spritesheets"), animation_directory, Animation::Type::kSpritesheet);
275 }
276 if (has_animations) {
277 if (!is_animation_known("idle")) {
278 throw GameDataError(
279 "Map object %s has animations but no idle animation", init_name.c_str());
280 }
281 assert(g_gr->animations().get_representative_image(name())->width() > 0);
282 }
283 if (table.has_key("icon")) {
284 icon_filename_ = table.get_string("icon");
285 if (icon_filename_.empty()) {
286 throw GameDataError("Map object %s has a menu icon, but it is empty", init_name.c_str());
287 }
288 }
289 check_representative_image();
290 }
~MapObjectDescr()291 MapObjectDescr::~MapObjectDescr() {
292 anims_.clear();
293 }
294
295 uint32_t MapObjectDescr::dyn_attribhigh_ = MapObject::HIGHEST_FIXED_ATTRIBUTE;
296 MapObjectDescr::AttribMap MapObjectDescr::dyn_attribs_;
297
is_animation_known(const std::string & animname) const298 bool MapObjectDescr::is_animation_known(const std::string& animname) const {
299 return (anims_.count(animname) == 1);
300 }
301
302 /**
303 * Add all animations for this map object
304 */
add_animations(const LuaTable & table,const std::string & animation_directory,Animation::Type anim_type)305 void MapObjectDescr::add_animations(const LuaTable& table,
306 const std::string& animation_directory,
307 Animation::Type anim_type) {
308 for (const std::string& animname : table.keys<std::string>()) {
309 try {
310 std::unique_ptr<LuaTable> anim = table.get_table(animname);
311 // TODO(GunChleoc): Maybe remove basename after conversion has been completed
312 const std::string basename =
313 anim->has_key<std::string>("basename") ? anim->get_string("basename") : animname;
314 const bool is_directional =
315 anim->has_key<std::string>("directional") ? anim->get_bool("directional") : false;
316 if (is_directional) {
317 for (int dir = 1; dir <= 6; ++dir) {
318 const std::string directional_animname =
319 animname + animation_direction_names[dir - 1];
320 if (is_animation_known(directional_animname)) {
321 throw GameDataError("Tried to add already existing directional animation '%s\'",
322 directional_animname.c_str());
323 }
324 const std::string directional_basename =
325 basename + animation_direction_names[dir - 1];
326 anims_.insert(std::pair<std::string, uint32_t>(
327 directional_animname, g_gr->animations().load(*anim, directional_basename,
328 animation_directory, anim_type)));
329 }
330 } else {
331 if (is_animation_known(animname)) {
332 throw GameDataError(
333 "Tried to add already existing animation '%s'", animname.c_str());
334 }
335 if (animname == "idle") {
336 anims_.insert(std::pair<std::string, uint32_t>(
337 animname,
338 g_gr->animations().load(name_, *anim, basename, animation_directory, anim_type)));
339 } else {
340 anims_.insert(std::pair<std::string, uint32_t>(
341 animname,
342 g_gr->animations().load(*anim, basename, animation_directory, anim_type)));
343 }
344 }
345 } catch (const std::exception& e) {
346 throw GameDataError("Error loading animation '%s' for map object '%s': %s",
347 animname.c_str(), name().c_str(), e.what());
348 }
349 }
350 }
351
assign_directional_animation(DirAnimations * anims,const std::string & basename)352 void MapObjectDescr::assign_directional_animation(DirAnimations* anims,
353 const std::string& basename) {
354 for (int32_t dir = 1; dir <= 6; ++dir) {
355 const std::string anim_name = basename + animation_direction_names[dir - 1];
356 try {
357 anims->set_animation(dir, get_animation(anim_name, nullptr));
358 } catch (const GameDataError& e) {
359 throw GameDataError("MO: Missing directional animation: %s", e.what());
360 }
361 }
362 }
363
get_animation(const std::string & animname,const MapObject *) const364 uint32_t MapObjectDescr::get_animation(const std::string& animname, const MapObject*) const {
365 std::map<std::string, uint32_t>::const_iterator it = anims_.find(animname);
366 if (it == anims_.end()) {
367 throw GameDataError("Unknown animation: %s for %s", animname.c_str(), name().c_str());
368 }
369 return it->second;
370 }
371
main_animation() const372 uint32_t MapObjectDescr::main_animation() const {
373 if (is_animation_known("idle")) {
374 return get_animation("idle", nullptr);
375 }
376 return !anims_.empty() ? anims_.begin()->second : 0;
377 }
378
get_animation_name(uint32_t const anim) const379 std::string MapObjectDescr::get_animation_name(uint32_t const anim) const {
380 for (const auto& temp_anim : anims_) {
381 if (temp_anim.second == anim) {
382 return temp_anim.first;
383 }
384 }
385 NEVER_HERE();
386 }
387
load_graphics() const388 void MapObjectDescr::load_graphics() const {
389 for (const auto& temp_anim : anims_) {
390 g_gr->animations().get_animation(temp_anim.second).load_default_scale_and_sounds();
391 }
392 }
393
representative_image(const RGBColor * player_color) const394 const Image* MapObjectDescr::representative_image(const RGBColor* player_color) const {
395 if (is_animation_known("idle")) {
396 return g_gr->animations().get_representative_image(
397 get_animation("idle", nullptr), player_color);
398 }
399 return nullptr;
400 }
401
check_representative_image()402 void MapObjectDescr::check_representative_image() {
403 if (representative_image() == nullptr) {
404 throw Widelands::GameDataError(
405 "The %s %s has no representative image. Does it have an \"idle\" animation?",
406 to_string(type()).c_str(), name().c_str());
407 }
408 }
409
icon() const410 const Image* MapObjectDescr::icon() const {
411 if (!icon_filename_.empty()) {
412 return g_gr->images().get(icon_filename_);
413 }
414 return nullptr;
415 }
icon_filename() const416 const std::string& MapObjectDescr::icon_filename() const {
417 return icon_filename_;
418 }
419
420 /**
421 * Search for the attribute in the attribute list
422 */
has_attribute(uint32_t const attr) const423 bool MapObjectDescr::has_attribute(uint32_t const attr) const {
424 for (const uint32_t& attrib : attributes_) {
425 if (attrib == attr) {
426 return true;
427 }
428 }
429 return false;
430 }
431
432 /**
433 * Add an attribute to the attribute list if it's not already there
434 */
add_attribute(uint32_t const attr)435 void MapObjectDescr::add_attribute(uint32_t const attr) {
436 if (!has_attribute(attr))
437 attributes_.push_back(attr);
438 }
439
add_attributes(const std::vector<std::string> & attributes,const std::set<uint32_t> & allowed_special)440 void MapObjectDescr::add_attributes(const std::vector<std::string>& attributes,
441 const std::set<uint32_t>& allowed_special) {
442 for (const std::string& attribute : attributes) {
443 uint32_t const attrib = get_attribute_id(attribute, true);
444 if (attrib < MapObject::HIGHEST_FIXED_ATTRIBUTE) {
445 if (!allowed_special.count(attrib)) {
446 throw GameDataError("bad attribute \"%s\"", attribute.c_str());
447 }
448 }
449 add_attribute(attrib);
450 }
451 }
452
453 /**
454 * Lookup an attribute by name. If the attribute name hasn't been encountered
455 * before and add_if_not_exists = true, we add it to the map. Else, throws exception.
456 */
get_attribute_id(const std::string & name,bool add_if_not_exists)457 uint32_t MapObjectDescr::get_attribute_id(const std::string& name, bool add_if_not_exists) {
458 AttribMap::iterator it = dyn_attribs_.find(name);
459
460 if (it != dyn_attribs_.end()) {
461 return it->second;
462 }
463
464 if (name == "worker") {
465 return MapObject::WORKER;
466 } else if (name == "resi") {
467 return MapObject::RESI;
468 }
469
470 if (!add_if_not_exists) {
471 throw GameDataError("get_attribute_id: attribute '%s' not found!\n", name.c_str());
472 } else {
473 ++dyn_attribhigh_;
474 dyn_attribs_[name] = dyn_attribhigh_;
475 }
476 assert(dyn_attribhigh_ != 0); // wrap around seems *highly* unlikely ;)
477
478 return dyn_attribhigh_;
479 }
480
481 /*
482 ==============================================================================
483
484 MapObject IMPLEMENTATION
485
486 ==============================================================================
487 */
488
489 /**
490 * Zero-initialize a map object
491 */
MapObject(const MapObjectDescr * const the_descr)492 MapObject::MapObject(const MapObjectDescr* const the_descr)
493 : descr_(the_descr), serial_(0), logsink_(nullptr), owner_(nullptr), reserved_by_worker_(false) {
494 }
495
496 /**
497 * Call this function if you want to remove the object immediately, without
498 * any effects.
499 */
remove(EditorGameBase & egbase)500 void MapObject::remove(EditorGameBase& egbase) {
501 removed(serial_); // Signal call
502 cleanup(egbase);
503 delete this;
504 }
505
506 /**
507 * Destroy the object immediately. Unlike remove(), special actions may be
508 * performed:
509 * \li Create a decaying skeleton (humans)
510 * \li Create a burning fire (buildings)
511 * \li ...
512 *
513 * \warning This function will immediately delete the memory allocated for
514 * the object. Therefore, it may be safer to call schedule_destroy()
515 * instead.
516 */
destroy(EditorGameBase & egbase)517 void MapObject::destroy(EditorGameBase& egbase) {
518 remove(egbase);
519 }
520
521 /**
522 * Schedule the object for immediate destruction.
523 * This can be used to safely destroy the object from within an act function.
524 */
schedule_destroy(Game & game)525 void MapObject::schedule_destroy(Game& game) {
526 game.cmdqueue().enqueue(new CmdDestroyMapObject(game.get_gametime(), *this));
527 }
528
529 /**
530 * Initialize the object by adding it to the object manager.
531 *
532 * \warning Make sure you call this from derived classes!
533 */
init(EditorGameBase & egbase)534 bool MapObject::init(EditorGameBase& egbase) {
535 egbase.objects().insert(this);
536 return true;
537 }
538
539 /**
540 * \warning Make sure you call this from derived classes!
541 */
cleanup(EditorGameBase & egbase)542 void MapObject::cleanup(EditorGameBase& egbase) {
543 egbase.objects().remove(*this);
544 }
545
do_draw_info(const InfoToDraw & info_to_draw,const std::string & census,const std::string & statictics,const Vector2f & field_on_dst,float scale,RenderTarget * dst) const546 void MapObject::do_draw_info(const InfoToDraw& info_to_draw,
547 const std::string& census,
548 const std::string& statictics,
549 const Vector2f& field_on_dst,
550 float scale,
551 RenderTarget* dst) const {
552 if (!(info_to_draw & (InfoToDraw::kCensus | InfoToDraw::kStatistics))) {
553 return;
554 }
555
556 // Rendering text is expensive, so let's just do it for only a few sizes.
557 const float granularity = 4.f;
558 float text_scale = scale;
559 // The formula is a bit fancy to avoid too much text overlap.
560 text_scale = std::round(granularity *
561 (text_scale > 1.f ? std::sqrt(text_scale) : std::pow(text_scale, 2.f))) /
562 granularity;
563
564 // Skip tiny text for performance reasons
565 if (text_scale < 0.5f) {
566 return;
567 }
568
569 UI::FontStyleInfo census_font(g_gr->styles().building_statistics_style().census_font());
570 census_font.set_size(scale * census_font.size());
571
572 // We always render this so we can have a stable position for the statistics string.
573 std::shared_ptr<const UI::RenderedText> rendered_census =
574 UI::g_fh->render(as_richtext_paragraph(census, census_font, UI::Align::kCenter), 120 * scale);
575 Vector2i position = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
576 if (info_to_draw & InfoToDraw::kCensus) {
577 rendered_census->draw(*dst, position, UI::Align::kCenter);
578 }
579
580 // Draw statistics if we want them, they are available and they fill fit
581 if (info_to_draw & InfoToDraw::kStatistics && !statictics.empty() && scale >= 0.5f) {
582 UI::FontStyleInfo statistics_font(
583 g_gr->styles().building_statistics_style().statistics_font());
584 statistics_font.set_size(scale * statistics_font.size());
585
586 std::shared_ptr<const UI::RenderedText> rendered_statistics =
587 UI::g_fh->render(as_richtext_paragraph(statictics, statistics_font, UI::Align::kCenter));
588 position.y += rendered_census->height() + text_height(statistics_font) / 4;
589 rendered_statistics->draw(*dst, position, UI::Align::kCenter);
590 }
591 }
592
representative_image() const593 const Image* MapObject::representative_image() const {
594 return descr().representative_image(get_owner() ? &get_owner()->get_playercolor() : nullptr);
595 }
596
597 /**
598 * Default implementation
599 */
get_training_attribute(TrainingAttribute) const600 int32_t MapObject::get_training_attribute(TrainingAttribute) const {
601 return -1;
602 }
603
604 /**
605 * Queue a CMD_ACT tdelta milliseconds from now, using the given data.
606 *
607 * \return The absolute gametime at which the CMD_ACT will occur.
608 */
schedule_act(Game & game,uint32_t const tdelta,uint32_t const data)609 uint32_t MapObject::schedule_act(Game& game, uint32_t const tdelta, uint32_t const data) {
610 if (tdelta < endless()) {
611 uint32_t const time = game.get_gametime() + tdelta;
612
613 game.cmdqueue().enqueue(new CmdAct(time, *this, data));
614
615 return time;
616 } else
617 return never();
618 }
619
620 /**
621 * Called when a CMD_ACT triggers.
622 */
act(Game &,uint32_t)623 void MapObject::act(Game&, uint32_t) {
624 }
625
626 /**
627 * Set the logsink. This should only be used by the debugging facilities.
628 */
set_logsink(LogSink * const sink)629 void MapObject::set_logsink(LogSink* const sink) {
630 logsink_ = sink;
631 }
632
log_general_info(const EditorGameBase &) const633 void MapObject::log_general_info(const EditorGameBase&) const {
634 }
635
636 /**
637 * Prints a log message prepended by the object's serial number.
638 */
molog(char const * fmt,...) const639 void MapObject::molog(char const* fmt, ...) const {
640 if (!g_verbose && !logsink_)
641 return;
642
643 va_list va;
644 char buffer[2048];
645
646 va_start(va, fmt);
647 vsnprintf(buffer, sizeof(buffer), fmt, va);
648 va_end(va);
649
650 if (logsink_)
651 logsink_->log(buffer);
652
653 log("MO(%u,%s): %s", serial_, descr().name().c_str(), buffer);
654 }
655
is_reserved_by_worker() const656 bool MapObject::is_reserved_by_worker() const {
657 return reserved_by_worker_;
658 }
659
set_reserved_by_worker(bool reserve)660 void MapObject::set_reserved_by_worker(bool reserve) {
661 reserved_by_worker_ = reserve;
662 }
663
664 constexpr uint8_t kCurrentPacketVersionMapObject = 2;
665
666 /**
667 * Load the entire data package from the given file.
668 * This will be called from the MapObject's derived class static load function.
669 *
670 * Derived functions must read all data into member variables, even if
671 * it is used only later in \ref load_pointers or \ref load_finish .
672 *
673 * Derived functions must call ancestor's function in the appropriate place.
674 */
load(FileRead & fr)675 void MapObject::Loader::load(FileRead& fr) {
676 try {
677 uint8_t const header = fr.unsigned_8();
678 if (header != HeaderMapObject)
679 throw wexception("header is %u, expected %u", header, HeaderMapObject);
680
681 uint8_t const packet_version = fr.unsigned_8();
682 // Supporting older versions for map loading
683 if (packet_version < 1 || packet_version > kCurrentPacketVersionMapObject) {
684 throw UnhandledVersionError("MapObject", packet_version, kCurrentPacketVersionMapObject);
685 }
686
687 Serial const serial = fr.unsigned_32();
688 try {
689 mol().register_object<MapObject>(serial, *get_object());
690 } catch (const WException& e) {
691 throw wexception("%u: %s", serial, e.what());
692 }
693
694 if (packet_version == kCurrentPacketVersionMapObject) {
695 get_object()->reserved_by_worker_ = fr.unsigned_8();
696 }
697 } catch (const WException& e) {
698 throw wexception("map object: %s", e.what());
699 }
700
701 egbase().objects().insert(get_object());
702 }
703
704 /**
705 * This will be called after all instances have been loaded.
706 *
707 * This is where pointers to other instances should be established, possibly
708 * using data that was previously stored in a member variable by \ref load .
709 *
710 * Derived functions must call ancestor's function in the appropriate place.
711 */
load_pointers()712 void MapObject::Loader::load_pointers() {
713 }
714
715 /**
716 * This will be called after all instances have been load_pointer'ed.
717 *
718 * This is where dependent data (e.g. ware requests) should be checked and
719 * configured.
720 *
721 * Derived functions must call ancestor's function in the appropriate place.
722 *
723 * We also preload some animation graphics here to prevent jitter at game start.
724 */
load_finish()725 void MapObject::Loader::load_finish() {
726 MapObject& mo = get<MapObject>();
727 mo.descr().load_graphics();
728 }
729
730 /**
731 * Save the MapObject to the given file.
732 */
save(EditorGameBase &,MapObjectSaver & mos,FileWrite & fw)733 void MapObject::save(EditorGameBase&, MapObjectSaver& mos, FileWrite& fw) {
734 fw.unsigned_8(HeaderMapObject);
735 fw.unsigned_8(kCurrentPacketVersionMapObject);
736
737 fw.unsigned_32(mos.get_object_file_index(*this));
738 fw.unsigned_8(reserved_by_worker_);
739 }
740
to_string(const MapObjectType type)741 std::string to_string(const MapObjectType type) {
742 // The types are documented in scripting/lua_map.cc -> LuaMapObjectDescription::get_type_name for
743 // the Lua interface, so make sure to change the documentation there when changing anything in
744 // this function.
745 switch (type) {
746 case MapObjectType::BOB:
747 return "bob";
748 case MapObjectType::CRITTER:
749 return "critter";
750 case MapObjectType::SHIP:
751 return "ship";
752 case MapObjectType::WORKER:
753 return "worker";
754 case MapObjectType::CARRIER:
755 return "carrier";
756 case MapObjectType::FERRY:
757 return "ferry";
758 case MapObjectType::SOLDIER:
759 return "soldier";
760 case MapObjectType::WARE:
761 return "ware";
762 case MapObjectType::BATTLE:
763 return "battle";
764 case MapObjectType::SHIP_FLEET:
765 return "ship_fleet";
766 case MapObjectType::FERRY_FLEET:
767 return "ferry_fleet";
768 case MapObjectType::IMMOVABLE:
769 return "immovable";
770 case MapObjectType::FLAG:
771 return "flag";
772 case MapObjectType::ROAD:
773 return "road";
774 case MapObjectType::WATERWAY:
775 return "waterway";
776 case MapObjectType::ROADBASE:
777 return "roadbase";
778 case MapObjectType::PORTDOCK:
779 return "portdock";
780 case MapObjectType::BUILDING:
781 return "building";
782 case MapObjectType::CONSTRUCTIONSITE:
783 return "constructionsite";
784 case MapObjectType::DISMANTLESITE:
785 return "dismantlesite";
786 case MapObjectType::WAREHOUSE:
787 return "warehouse";
788 case MapObjectType::MARKET:
789 return "market";
790 case MapObjectType::PRODUCTIONSITE:
791 return "productionsite";
792 case MapObjectType::MILITARYSITE:
793 return "militarysite";
794 case MapObjectType::TRAININGSITE:
795 return "trainingsite";
796 case MapObjectType::MAPOBJECT:
797 throw wexception("Unknown MapObjectType %d.", static_cast<int>(type));
798 }
799 NEVER_HERE();
800 }
801 } // namespace Widelands
802