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