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/tribes/worker.h"
21 
22 #include <memory>
23 #include <tuple>
24 
25 #include "base/log.h"
26 #include "base/macros.h"
27 #include "base/wexception.h"
28 #include "economy/economy.h"
29 #include "economy/flag.h"
30 #include "economy/portdock.h"
31 #include "economy/road.h"
32 #include "economy/transfer.h"
33 #include "graphic/graphic.h"
34 #include "graphic/rendertarget.h"
35 #include "graphic/text_layout.h"
36 #include "io/fileread.h"
37 #include "io/filewrite.h"
38 #include "logic/cmd_incorporate.h"
39 #include "logic/game.h"
40 #include "logic/game_controller.h"
41 #include "logic/game_data_error.h"
42 #include "logic/map_objects/checkstep.h"
43 #include "logic/map_objects/findbob.h"
44 #include "logic/map_objects/findimmovable.h"
45 #include "logic/map_objects/findnode.h"
46 #include "logic/map_objects/terrain_affinity.h"
47 #include "logic/map_objects/tribes/carrier.h"
48 #include "logic/map_objects/tribes/dismantlesite.h"
49 #include "logic/map_objects/tribes/market.h"
50 #include "logic/map_objects/tribes/soldier.h"
51 #include "logic/map_objects/tribes/tribe_descr.h"
52 #include "logic/map_objects/tribes/warehouse.h"
53 #include "logic/map_objects/tribes/worker_program.h"
54 #include "logic/map_objects/world/critter.h"
55 #include "logic/map_objects/world/resource_description.h"
56 #include "logic/map_objects/world/terrain_description.h"
57 #include "logic/map_objects/world/world.h"
58 #include "logic/mapfringeregion.h"
59 #include "logic/message_queue.h"
60 #include "logic/player.h"
61 #include "map_io/map_object_loader.h"
62 #include "map_io/map_object_saver.h"
63 #include "map_io/tribes_legacy_lookup_table.h"
64 #include "sound/note_sound.h"
65 
66 namespace Widelands {
67 
68 /**
69  * createware=\<waretype\>
70  *
71  * The worker will create and carry an ware of the given type.
72  *
73  * sparam1 = ware name
74  */
run_createware(Game & game,State & state,const Action & action)75 bool Worker::run_createware(Game& game, State& state, const Action& action) {
76 
77 	if (WareInstance* const ware = fetch_carried_ware(game)) {
78 		molog("  Still carrying a ware! Delete it.\n");
79 		ware->schedule_destroy(game);
80 	}
81 
82 	Player& player = *get_owner();
83 	DescriptionIndex const wareid(action.iparam1);
84 	WareInstance& ware = *new WareInstance(wareid, player.tribe().get_ware_descr(wareid));
85 	ware.init(game);
86 
87 	set_carried_ware(game, &ware);
88 
89 	// For statistics, inform the user that a ware was produced
90 	player.ware_produced(wareid);
91 
92 	++state.ivar1;
93 	schedule_act(game, 10);
94 	return true;
95 }
96 
97 /**
98  * Mine on the current coordinates for resources decrease, go home.
99  *
100  * Syntax in conffile: mine=\<resource\> \<area\>
101  *
102  * \param g
103  * \param state
104  * \param action Which resource to mine (action.sparam1) and where to look for
105  * it (in a radius of action.iparam1 around current location)
106  */
107 // TODO(unknown): Lots of magic numbers in here
run_mine(Game & game,State & state,const Action & action)108 bool Worker::run_mine(Game& game, State& state, const Action& action) {
109 	Map* map = game.mutable_map();
110 
111 	// Make sure that the specified resource is available in this world
112 	DescriptionIndex const res = game.world().resource_index(action.sparam1.c_str());
113 	if (res == Widelands::INVALID_INDEX)
114 		throw GameDataError(_("should mine resource %s, which does not exist in world; tribe "
115 		                      "is not compatible with world"),
116 		                    action.sparam1.c_str());
117 
118 	// Select one of the fields randomly
119 	uint32_t totalres = 0;
120 	uint32_t totalchance = 0;
121 	int32_t pick;
122 	MapRegion<Area<FCoords>> mr(
123 	   *map, Area<FCoords>(map->get_fcoords(get_position()), action.iparam1));
124 	do {
125 		DescriptionIndex fres = mr.location().field->get_resources();
126 		ResourceAmount amount = mr.location().field->get_resources_amount();
127 
128 		// In the future, we might want to support amount = 0 for
129 		// fields that can produce an infinite amount of resources.
130 		// Rather -1 or something similar. not 0
131 		if (fres != res)
132 			amount = 0;
133 
134 		totalres += amount;
135 		totalchance += 8 * amount;
136 
137 		// Add penalty for fields that are running out
138 		// Except for totally depleted fields or wrong ressource fields
139 		// if we already know there is no ressource (left) we won't mine there
140 		if (amount > 0) {
141 			if (amount <= 2) {
142 				totalchance += 6;
143 			} else if (amount <= 4) {
144 				totalchance += 4;
145 			} else if (amount <= 6) {
146 				totalchance += 2;
147 			}
148 		}
149 	} while (mr.advance(*map));
150 
151 	if (totalres == 0) {
152 		molog("  Run out of resources\n");
153 		send_signal(game, "fail");  //  mine empty, abort program
154 		pop_task(game);
155 		return true;
156 	}
157 
158 	// Second pass through fields - reset mr
159 	pick = game.logic_rand() % totalchance;
160 	mr = MapRegion<Area<FCoords>>(
161 	   *map, Area<FCoords>(map->get_fcoords(get_position()), action.iparam1));
162 	do {
163 		DescriptionIndex fres = mr.location().field->get_resources();
164 		if (fres != res) {
165 			continue;
166 		}
167 
168 		ResourceAmount amount = mr.location().field->get_resources_amount();
169 
170 		pick -= 8 * amount;
171 		if (pick < 0) {
172 			assert(amount > 0);
173 
174 			--amount;
175 			map->set_resources(mr.location(), amount);
176 			break;
177 		}
178 	} while (mr.advance(*map));
179 
180 	if (pick >= 0) {
181 		molog("  Not successful this time\n");
182 		send_signal(game, "fail");  //  not successful, abort program
183 		pop_task(game);
184 		return true;
185 	}
186 
187 	// Advance program state
188 	++state.ivar1;
189 	schedule_act(game, 10);
190 	return true;
191 }
192 
193 /**
194  * Breed on the current coordinates for resource increase, go home.
195  *
196  * Syntax in conffile: breed=\<resource\> \<area\>
197  *
198  * \param g
199  * \param state
200  * \param action Which resource to breed (action.sparam1) and where to put
201  * it (in a radius of action.iparam1 around current location)
202  */
203 
204 // TODO(unknown): in FindNodeResourceBreedable, the node (or neighbors) is accepted if it is
205 // breedable.
206 // In here, breeding may happen on a node emptied of resource.
207 // TODO(unknown): Lots of magic numbers in here
208 // TODO(unknown): Document parameters g and state
run_breed(Game & game,State & state,const Action & action)209 bool Worker::run_breed(Game& game, State& state, const Action& action) {
210 	molog(" Breed(%s, %i)\n", action.sparam1.c_str(), action.iparam1);
211 
212 	Map* map = game.mutable_map();
213 
214 	// Make sure that the specified resource is available in this world
215 	DescriptionIndex const res = game.world().resource_index(action.sparam1.c_str());
216 	if (res == Widelands::INVALID_INDEX)
217 		throw GameDataError(_("should breed resource type %s, which does not exist in world; "
218 		                      "tribe is not compatible with world"),
219 		                    action.sparam1.c_str());
220 
221 	// Select one of the fields randomly
222 	uint32_t totalres = 0;
223 	uint32_t totalchance = 0;
224 	int32_t pick;
225 	MapRegion<Area<FCoords>> mr(
226 	   *map, Area<FCoords>(map->get_fcoords(get_position()), action.iparam1));
227 	do {
228 		DescriptionIndex fres = mr.location().field->get_resources();
229 		ResourceAmount amount = mr.location().field->get_initial_res_amount() -
230 		                        mr.location().field->get_resources_amount();
231 
232 		// In the future, we might want to support amount = 0 for
233 		// fields that can produce an infinite amount of resources.
234 		// Rather -1 or something similar. not 0
235 		if (fres != res)
236 			amount = 0;
237 
238 		totalres += amount;
239 		totalchance += 8 * amount;
240 
241 		// Add penalty for fields that are running out
242 		if (amount == 0)
243 			// we already know it's completely empty, so punish is less
244 			++totalchance;
245 		else if (amount <= 2)
246 			totalchance += 6;
247 		else if (amount <= 4)
248 			totalchance += 4;
249 		else if (amount <= 6)
250 			totalchance += 2;
251 	} while (mr.advance(*map));
252 
253 	if (totalres == 0) {
254 		molog("  All resources full\n");
255 		send_signal(game, "fail");  //  no space for more, abort program
256 		pop_task(game);
257 		return true;
258 	}
259 
260 	// Second pass through fields - reset mr!
261 	assert(totalchance);
262 	pick = game.logic_rand() % totalchance;
263 	mr = MapRegion<Area<FCoords>>(
264 	   *map, Area<FCoords>(map->get_fcoords(get_position()), action.iparam1));
265 
266 	do {
267 		DescriptionIndex fres = mr.location().field->get_resources();
268 		if (fres != res)
269 			continue;
270 
271 		ResourceAmount amount = mr.location().field->get_initial_res_amount() -
272 		                        mr.location().field->get_resources_amount();
273 
274 		pick -= 8 * amount;
275 		if (pick < 0) {
276 			assert(amount > 0);
277 
278 			--amount;
279 			map->set_resources(mr.location(), mr.location().field->get_initial_res_amount() - amount);
280 			break;
281 		}
282 	} while (mr.advance(*map));
283 
284 	if (pick >= 0) {
285 		molog("  Not successful this time\n");
286 		send_signal(game, "fail");  //  not successful, abort program
287 		pop_task(game);
288 		return true;
289 	}
290 
291 	molog("  Successfully bred\n");
292 
293 	// Advance program state
294 	++state.ivar1;
295 	schedule_act(game, 10);
296 	return true;
297 }
298 
299 /**
300  * findobject=key:value key:value ...
301  *
302  * Find and select an object based on a number of predicates.
303  * The object can be used in other commands like walk or object.
304  *
305  * Predicates:
306  * radius:\<dist\>
307  * Find objects within the given radius
308  *
309  * attrib:\<attribute\>  (optional)
310  * Find objects with the given attribute
311  *
312  * type:\<what\>         (optional, defaults to immovable)
313  * Find only objects of this type
314  *
315  * iparam1 = radius predicate
316  * iparam2 = attribute predicate (if >= 0)
317  * sparam1 = type
318  */
run_findobject(Game & game,State & state,const Action & action)319 bool Worker::run_findobject(Game& game, State& state, const Action& action) {
320 	CheckStepWalkOn cstep(descr().movecaps(), false);
321 
322 	const Map& map = game.map();
323 	Area<FCoords> area(map.get_fcoords(get_position()), 0);
324 	bool found_reserved = false;
325 
326 	for (;; ++area.radius) {
327 		if (action.iparam1 < area.radius) {
328 			send_signal(game, "fail");  //  no object found, cannot run program
329 			pop_task(game);
330 			if (upcast(ProductionSite, productionsite, get_location(game))) {
331 				if (!found_reserved) {
332 					productionsite->notify_player(game, 30);
333 				} else {
334 					productionsite->unnotify_player();
335 				}
336 			}
337 			return true;
338 		}
339 		if (action.sparam1 == "immovable") {
340 			if (upcast(ProductionSite, productionsite, get_location(game))) {
341 				productionsite->unnotify_player();
342 			}
343 			std::vector<ImmovableFound> list;
344 			if (action.iparam2 < 0)
345 				map.find_reachable_immovables(game, area, &list, cstep);
346 			else
347 				map.find_reachable_immovables(
348 				   game, area, &list, cstep, FindImmovableAttribute(action.iparam2));
349 
350 			for (int idx = list.size() - 1; idx >= 0; idx--) {
351 				if (upcast(Immovable, imm, list[idx].object)) {
352 					if (imm->is_reserved_by_worker()) {
353 						found_reserved = true;
354 						list.erase(list.begin() + idx);
355 					} else {
356 						Coords const coord = imm->get_position();
357 						MapIndex mapidx = map.get_index(coord, map.get_width());
358 						Vision const visible = owner().vision(mapidx);
359 						if (!visible) {
360 							list.erase(list.begin() + idx);
361 						}
362 					}
363 				}
364 			}
365 
366 			if (!list.empty()) {
367 				set_program_objvar(game, state, list[game.logic_rand() % list.size()].object);
368 				break;
369 			}
370 		} else {
371 			if (upcast(ProductionSite, productionsite, get_location(game))) {
372 				productionsite->unnotify_player();
373 			}
374 			std::vector<Bob*> list;
375 			if (action.iparam2 < 0)
376 				map.find_reachable_bobs(game, area, &list, cstep);
377 			else
378 				map.find_reachable_bobs(game, area, &list, cstep, FindBobAttribute(action.iparam2));
379 
380 			for (int idx = list.size() - 1; idx >= 0; idx--) {
381 				if (upcast(MapObject, bob, list[idx])) {
382 					if (bob->is_reserved_by_worker()) {
383 						found_reserved = true;
384 						list.erase(list.begin() + idx);
385 					}
386 				}
387 			}
388 			if (!list.empty()) {
389 				set_program_objvar(game, state, list[game.logic_rand() % list.size()]);
390 				break;
391 			}
392 		}
393 	}
394 	++state.ivar1;
395 	schedule_act(game, 10);
396 	return true;
397 }
398 
399 /**
400  * Care about the game.forester_cache_.
401  *
402  * Making the run_findspace routine shorter, by putting one special case into its own function.
403  * This gets called many times each time the forester searches for a place for a sapling.
404  * Since this already contains three nested for-loops, I dedided to cache the values for a
405  * cpu/memory tradeoff (hint: Widelands is pretty heavy on my oldish PC).
406  * Since the implementation details of double could vary between platforms, I decided to
407  * quantize the terrain goodness into int16_t. This lowers the footprint of the caching,
408  * and also makes desyncs caused by different floats horribly unlikely.
409  *
410  * The forester_cache_ is sparse, but then, lookups are fast.
411  *
412  * At the moment of writing, map changing is really infrequent (only in two scenarios)
413  * and even those do not affect this. However, since map changes are possible, this
414  * checks the reliability of the cached value with a small probability (~1%), If a
415  * disparency is found, the entire cache is nuked.
416  *
417  * If somebody in the future makes a scenario, where the land first is barren, and then
418  * spots of eden show up, the foresters will not immediately notice (because of the cache).
419  * They will eventually notice, and since the instance is shared between tribes,
420  * all foresters notice this at the same moment, also in network play. I hope this is okay.
421  *
422  */
findspace_helper_for_forester(const Coords & pos,const Map & map,Game & game)423 int16_t Worker::findspace_helper_for_forester(const Coords& pos, const Map& map, Game& game) {
424 
425 	std::vector<int16_t>& forester_cache = game.forester_cache_;
426 	const unsigned vecsize = 1 + unsigned(map.max_index());
427 	const MapIndex mi = map.get_index(pos, map.get_width());
428 	const FCoords fpos = map.get_fcoords(pos);
429 	// This if-statement should be true only once per game.
430 	if (vecsize != forester_cache.size()) {
431 		forester_cache.resize(vecsize, kInvalidForesterEntry);
432 	}
433 	int16_t cache_entry = forester_cache[mi];
434 	bool x_check = false;
435 	assert(cache_entry >= kInvalidForesterEntry);
436 	if (cache_entry != kInvalidForesterEntry) {
437 		if (0 == ((game.logic_rand()) & 0xfe)) {
438 			// Cached value found, but exceptionally not trusted.
439 			x_check = true;
440 		} else {
441 			// Found the terrain forestability, no more work to do
442 			return cache_entry;
443 		}
444 	}
445 
446 	// Okay, I do not know whether this terrain suits. Let's obtain the value (and then cache it)
447 
448 	const DescriptionMaintainer<ImmovableDescr>& immovables = game.world().immovables();
449 
450 	// TODO(kxq): could the tree_sapling come from config? Currently, there is only one sparam..
451 	// TODO(k.halfmann): avoid fetching this vlaues every time, as it is const during runtime?.
452 	// This code is only executed at cache miss.
453 	const uint32_t attribute_id = ImmovableDescr::get_attribute_id("tree_sapling");
454 
455 	const DescriptionMaintainer<TerrainDescription>& terrains = game.world().terrains();
456 	int best = 0;
457 	for (DescriptionIndex i = 0; i < immovables.size(); ++i) {
458 		const ImmovableDescr& immovable_descr = immovables.get(i);
459 		if (immovable_descr.has_attribute(attribute_id) && immovable_descr.has_terrain_affinity()) {
460 			int probability =
461 			   probability_to_grow(immovable_descr.terrain_affinity(), fpos, map, terrains);
462 			if (probability > best) {
463 				best = probability;
464 			}
465 		}
466 	}
467 	// normalize value to int16 range
468 	const int16_t correct_val = (std::numeric_limits<int16_t>::max() - 1) *
469 	                            (static_cast<double>(best) / TerrainAffinity::kPrecisionFactor);
470 
471 	if (x_check && (correct_val != cache_entry)) {
472 		forester_cache.clear();
473 		forester_cache.resize(vecsize, kInvalidForesterEntry);
474 	}
475 	forester_cache[mi] = correct_val;
476 	return correct_val;
477 }
478 
479 /**
480  * findspace key:value key:value ...
481  *
482  * Find a node based on a number of predicates.
483  * The node can later be used in other commands, e.g. walk.
484  *
485  * Predicates:
486  * radius:\<dist\>
487  * Search for nodes within the given radius around the worker.
488  *
489  * size:[any|build|small|medium|big|mine|port]
490  * Search for fields with the given amount of space.
491  *
492  * resource:\<resname\>
493  * Resource to search for. This is mainly intended for fisher and
494  * therelike (non detectable Resources and default resources)
495  *
496  * space
497  * Find only nodes that are walkable such that all neighbours
498  * are also walkable (an exception is made if one of the neighbouring
499  * fields is owned by this worker's location).
500  *
501  *
502  * iparam1 = radius
503  * iparam2 = FindNodeSize::sizeXXX
504  * iparam3 = whether the "space" flag is set
505  * iparam4 = whether the "breed" flag is set
506  * sparam1 = Resource
507  */
508 // TODO(unknown): This is an embarrasingly ugly hack to make bug #1796611 happen less
509 // often. But it gives no passability guarantee (that workers will not
510 // get locked in). For example one farmer may call findspace and then,
511 // before he plants anything, another farmer may call findspace, which
512 // may find a space without considering that the first farmer will plant
513 // something. Together they can cause a passability problem. This code
514 // will also allow blocking the shoreline if it is next to the worker's
515 // location. Also, the gap of 2 nodes between 2 farms will be blocked,
516 // because both are next to their farm. The only real solution that I can
517 // think of for this kind of bugs is to only allow unwalkable objects to
518 // be placed on a node if ALL neighbouring nodes are passable. This must
519 // of course be checked at the moment when the object is placed and not,
520 // as in this case, only before a worker starts walking there to place an
521 // object. But that would make it very difficult to find space for things
522 // like farm fileds. So our only option seems to be to keep all farm
523 // fields, trees, rocks and such on triangles and keep the nodes
524 // passable. See code structure issue #1096824.
525 struct FindNodeSpace {
FindNodeSpaceWidelands::FindNodeSpace526 	explicit FindNodeSpace() {
527 	}
528 
acceptWidelands::FindNodeSpace529 	bool accept(const EditorGameBase& egbase, const FCoords& coords) const {
530 		if (!(coords.field->nodecaps() & MOVECAPS_WALK))
531 			return false;
532 
533 		for (uint8_t dir = FIRST_DIRECTION; dir <= LAST_DIRECTION; ++dir) {
534 			FCoords const neighb = egbase.map().get_neighbour(coords, dir);
535 
536 			if (!(neighb.field->maxcaps() & MOVECAPS_WALK))
537 				return false;
538 		}
539 
540 		return true;
541 	}
542 };
543 
run_findspace(Game & game,State & state,const Action & action)544 bool Worker::run_findspace(Game& game, State& state, const Action& action) {
545 	std::vector<Coords> list;
546 	const Map& map = game.map();
547 	const World& world = game.world();
548 
549 	CheckStepDefault cstep(descr().movecaps());
550 
551 	Area<FCoords> area(map.get_fcoords(get_position()), action.iparam1);
552 
553 	FindNodeAnd functor;
554 	functor.add(FindNodeSize(static_cast<FindNodeSize::Size>(action.iparam2)));
555 	if (action.sparam1.size()) {
556 		if (action.iparam4) {
557 			functor.add(FindNodeResourceBreedable(world.resource_index(action.sparam1.c_str())));
558 		} else {
559 			functor.add(FindNodeResource(world.resource_index(action.sparam1.c_str())));
560 		}
561 	}
562 
563 	if (action.iparam5 > -1)
564 		functor.add(FindNodeImmovableAttribute(action.iparam5), true);
565 
566 	if (action.iparam3)
567 		functor.add(FindNodeSpace());
568 
569 	if (action.iparam7)
570 		functor.add(FindNodeTerraform());
571 
572 	if (!map.find_reachable_fields(game, area, &list, cstep, functor)) {
573 
574 		// This is default note "out of resources" sent to a player
575 		FailNotificationType fail_notification_type = FailNotificationType::kDefault;
576 
577 		// In case this is a fishbreeder, we do more checks
578 		if (action.sparam1.size() && action.iparam4) {
579 
580 			// We need to create create another functor that will look for nodes full of fish
581 			FindNodeAnd functorAnyFull;
582 			functorAnyFull.add(FindNodeSize(static_cast<FindNodeSize::Size>(action.iparam2)));
583 			functorAnyFull.add(FindNodeResourceBreedable(
584 			   world.resource_index(action.sparam1.c_str()), AnimalBreedable::kAnimalFull));
585 			if (action.iparam5 > -1)
586 				functorAnyFull.add(FindNodeImmovableAttribute(action.iparam5), true);
587 
588 			if (action.iparam3)
589 				functorAnyFull.add(FindNodeSpace());
590 
591 			// If there are fields full of fish, we change the type of notification
592 			if (map.find_reachable_fields(game, area, &list, cstep, functorAnyFull)) {
593 				fail_notification_type = FailNotificationType::kFull;
594 			}
595 		}
596 		switch (fail_notification_type) {
597 		case FailNotificationType::kFull:
598 			molog("  all reachable nodes are full\n");
599 			break;
600 		default:
601 			molog("  no space found\n");
602 		}
603 
604 		if (upcast(ProductionSite, productionsite, get_location(game)))
605 			productionsite->notify_player(game, 30, fail_notification_type);
606 
607 		send_signal(game, "fail");
608 		pop_task(game);
609 		return true;
610 	} else {
611 		if (upcast(ProductionSite, productionsite, get_location(game)))
612 			productionsite->unnotify_player();
613 	}
614 
615 	// Pick a location at random
616 	state.coords = list[game.logic_rand() % list.size()];
617 
618 	// Special case: forester checks multiple locations instead of one.
619 	if (1 < action.iparam6) {
620 		// In the bug comments, somebody asked for unequal quality for the foresters of various
621 		// tribes to find the best spot. Here stubborness is the number of slots to consider.
622 		int stubborness = action.iparam6;
623 		int16_t best = findspace_helper_for_forester(state.coords, map, game);
624 		while (1 < stubborness--) {
625 			const Coords altpos = list[game.logic_rand() % list.size()];
626 			const int16_t alt_pos_goodness = findspace_helper_for_forester(altpos, map, game);
627 			if (alt_pos_goodness > best) {
628 				best = alt_pos_goodness;
629 				state.coords = altpos;
630 			}
631 		}
632 	}
633 
634 	++state.ivar1;
635 	schedule_act(game, 10);
636 	return true;
637 }
638 
639 /**
640  * walk=\<where\>
641  *
642  * Walk to a previously selected destination. where can be one of:
643  * object  walk to a previously found and selected object
644  * coords  walk to a previously found and selected field/coordinate
645  *
646  * iparam1 = walkXXX
647  */
run_walk(Game & game,State & state,const Action & action)648 bool Worker::run_walk(Game& game, State& state, const Action& action) {
649 	BaseImmovable const* const imm = game.map()[get_position()].get_immovable();
650 	Coords dest(Coords::null());
651 	bool forceonlast = false;
652 	int32_t max_steps = -1;
653 
654 	// First of all, make sure we're outside
655 	if (imm == &dynamic_cast<Building&>(*get_location(game))) {
656 		start_task_leavebuilding(game, false);
657 		return true;
658 	}
659 
660 	// Determine the coords we need to walk towards
661 	if (action.iparam1 & Action::walkObject) {
662 		MapObject* const obj = state.objvar1.get(game);
663 
664 		if (obj) {
665 			if (upcast(Bob const, bob, obj))
666 				dest = bob->get_position();
667 			else if (upcast(Immovable const, immovable, obj))
668 				dest = immovable->get_position();
669 			else
670 				throw wexception("MO(%u): [actWalk]: bad object type %s", serial(),
671 				                 to_string(obj->descr().type()).c_str());
672 
673 			//  Only take one step, then rethink (object may have moved)
674 			max_steps = 1;
675 
676 			forceonlast = true;
677 		}
678 	}
679 	if (!dest && (action.iparam1 & Action::walkCoords)) {
680 		dest = state.coords;
681 	}
682 	if (!dest) {
683 		send_signal(game, "fail");
684 		pop_task(game);
685 		return true;
686 	}
687 
688 	// If we've already reached our destination, that's cool
689 	if (get_position() == dest) {
690 		++state.ivar1;
691 		return false;  // next instruction
692 	}
693 
694 	// Walk towards it
695 	if (!start_task_movepath(game, dest, 10, descr().get_right_walk_anims(does_carry_ware(), this),
696 	                         forceonlast, max_steps)) {
697 		molog("  could not find path\n");
698 		send_signal(game, "fail");
699 		pop_task(game);
700 		return true;
701 	}
702 
703 	return true;
704 }
705 
706 /**
707  * animate=\<name\> \<duration\>
708  *
709  * Play the given animation for the given amount of time.
710  *
711  * iparam1 = anim id
712  * iparam2 = duration
713  */
run_animate(Game & game,State & state,const Action & action)714 bool Worker::run_animate(Game& game, State& state, const Action& action) {
715 	set_animation(game, action.iparam1);
716 
717 	++state.ivar1;
718 	schedule_act(game, action.iparam2);
719 	return true;
720 }
721 
722 /**
723  * Return home, drop any ware we're carrying onto our building's flag.
724  *
725  * iparam1 = 0: don't drop ware on flag, 1: do drop ware on flag
726  */
run_return(Game & game,State & state,const Action & action)727 bool Worker::run_return(Game& game, State& state, const Action& action) {
728 	++state.ivar1;
729 	start_task_return(game, action.iparam1);
730 	return true;
731 }
732 
733 /**
734  * callobject=\<command\>
735  *
736  * Cause the currently selected object to execute the given program.
737  *
738  * sparam1 = object command name
739  */
run_callobject(Game & game,State & state,const Action & action)740 bool Worker::run_callobject(Game& game, State& state, const Action& action) {
741 	MapObject* const obj = state.objvar1.get(game);
742 
743 	if (!obj) {
744 		send_signal(game, "fail");
745 		pop_task(game);
746 		return true;
747 	}
748 
749 	if (upcast(Immovable, immovable, obj))
750 		immovable->switch_program(game, action.sparam1);
751 	else if (upcast(Bob, bob, obj)) {
752 		if (upcast(Critter, crit, bob)) {
753 			crit->reset_tasks(game);  //  TODO(unknown): ask the critter more nicely
754 			crit->start_task_program(game, action.sparam1);
755 		} else if (upcast(Worker, w, bob)) {
756 			w->reset_tasks(game);  //  TODO(unknown): ask the worker more nicely
757 			w->start_task_program(game, action.sparam1);
758 		} else
759 			throw wexception("MO(%i): [actObject]: bad bob type %s", serial(),
760 			                 to_string(bob->descr().type()).c_str());
761 	} else
762 		throw wexception("MO(%u): [actObject]: bad object type %s", serial(),
763 		                 to_string(obj->descr().type()).c_str());
764 
765 	++state.ivar1;
766 	schedule_act(game, 10);
767 	return true;
768 }
769 
770 /**
771  * Plant an immovable on the current position. The immovable type must have
772  * been selected by a previous command (i.e. plant)
773  */
run_plant(Game & game,State & state,const Action & action)774 bool Worker::run_plant(Game& game, State& state, const Action& action) {
775 	assert(action.sparamv.size());
776 
777 	if (action.iparam1 == Action::plantUnlessObject) {
778 		if (state.objvar1.get(game)) {
779 			// already have an object, so don't create a new one
780 			++state.ivar1;
781 			schedule_act(game, 10);
782 			return true;
783 		}
784 	}
785 
786 	const Map& map = game.map();
787 	Coords pos = get_position();
788 	FCoords fpos = map.get_fcoords(pos);
789 
790 	// Check if the map is still free here
791 	if (BaseImmovable const* const imm = map[pos].get_immovable())
792 		if (imm->get_size() >= BaseImmovable::SMALL) {
793 			molog("  field no longer free\n");
794 			send_signal(game, "fail");
795 			pop_task(game);
796 			return true;
797 		}
798 
799 	// Figure the (at most) six best fitting immovables (as judged by terrain
800 	// affinity). We will pick one of them at random later. The container is
801 	// picked to be a stable sorting one, so that no deyncs happen in
802 	// multiplayer.
803 	std::set<std::tuple<int, DescriptionIndex, MapObjectDescr::OwnerType>>
804 	   best_suited_immovables_index;
805 
806 	// Checks if the 'immovable_description' has a terrain_affinity, if so use it. Otherwise assume
807 	// it to be 1 (perfect fit). Adds it to the best_suited_immovables_index.
808 	const auto test_suitability = [&best_suited_immovables_index, &fpos, &map, &game](
809 	   const uint32_t attribute_id, const DescriptionIndex index,
810 	   const ImmovableDescr& immovable_description, MapObjectDescr::OwnerType owner_type) {
811 		if (!immovable_description.has_attribute(attribute_id)) {
812 			return;
813 		}
814 		int p = TerrainAffinity::kPrecisionFactor;
815 		if (immovable_description.has_terrain_affinity()) {
816 			p = probability_to_grow(
817 			   immovable_description.terrain_affinity(), fpos, map, game.world().terrains());
818 		}
819 		best_suited_immovables_index.insert(std::make_tuple(p, index, owner_type));
820 		if (best_suited_immovables_index.size() > 6) {
821 			best_suited_immovables_index.erase(best_suited_immovables_index.begin());
822 		}
823 	};
824 
825 	if (action.sparamv.empty()) {
826 		throw GameDataError("plant needs at least one attrib:<attribute>.");
827 	}
828 
829 	// Collect all world and tribe immovable types for all the attributes along with a suitability
830 	// metric
831 	for (const std::string& attrib : action.sparamv) {
832 		if (attrib.empty()) {
833 			throw GameDataError("plant has an empty attrib:<attribute>");
834 		}
835 		const uint32_t attribute_id = ImmovableDescr::get_attribute_id(attrib);
836 
837 		// Add world immovables
838 		const DescriptionMaintainer<ImmovableDescr>& world_immovables = game.world().immovables();
839 		for (uint32_t i = 0; i < world_immovables.size(); ++i) {
840 			test_suitability(
841 			   attribute_id, i, world_immovables.get(i), MapObjectDescr::OwnerType::kWorld);
842 		}
843 
844 		// Add tribe immovables
845 		for (const DescriptionIndex i : owner().tribe().immovables()) {
846 			test_suitability(attribute_id, i, *owner().tribe().get_immovable_descr(i),
847 			                 MapObjectDescr::OwnerType::kTribe);
848 		}
849 	}
850 
851 	if (best_suited_immovables_index.empty()) {
852 		molog("  WARNING: No suitable immovable found!\n");
853 		send_signal(game, "fail");
854 		pop_task(game);
855 		return true;
856 	}
857 
858 	// Randomly pick one of the immovables to be planted.
859 
860 	// Each candidate is weighted by its probability to grow.
861 	int total_weight = 0;
862 	for (const auto& bsii : best_suited_immovables_index) {
863 		const int weight = std::get<0>(bsii);
864 		total_weight += weight;
865 	}
866 
867 	int choice = game.logic_rand() % total_weight;
868 	for (const auto& bsii : best_suited_immovables_index) {
869 		const int weight = std::get<0>(bsii);
870 		state.ivar2 = std::get<1>(bsii);
871 		state.ivar3 = static_cast<int>(std::get<2>(bsii));
872 		choice -= weight;
873 		if (0 > choice) {
874 			break;
875 		}
876 	}
877 
878 	Immovable& newimm = game.create_immovable(
879 	   pos, state.ivar2, static_cast<Widelands::MapObjectDescr::OwnerType>(state.ivar3),
880 	   get_owner());
881 
882 	if (action.iparam1 == Action::plantUnlessObject) {
883 		state.objvar1 = &newimm;
884 	}
885 
886 	++state.ivar1;
887 	schedule_act(game, 10);
888 	return true;
889 }
890 
891 /**
892  * createbob=\<bob name\> \<bob name\> ...
893  *
894  * Plants a bob (critter usually, maybe also worker later on), randomly selected from one of the
895  * given types.
896  *
897  * sparamv = possible bobs
898  */
run_createbob(Game & game,State & state,const Action & action)899 bool Worker::run_createbob(Game& game, State& state, const Action& action) {
900 	int32_t const idx = game.logic_rand() % action.sparamv.size();
901 
902 	const std::string& bob = action.sparamv[idx];
903 	const DescriptionIndex critter = game.world().get_critter(bob.c_str());
904 
905 	if (critter == INVALID_INDEX) {
906 		molog("  WARNING: Unknown bob %s\n", bob.c_str());
907 		send_signal(game, "fail");
908 		pop_task(game);
909 		return true;
910 	}
911 
912 	game.create_critter(get_position(), critter);
913 	++state.ivar1;
914 	schedule_act(game, 10);
915 	return true;
916 }
917 
run_terraform(Game & game,State & state,const Action &)918 bool Worker::run_terraform(Game& game, State& state, const Action&) {
919 	const World& world = game.world();
920 	std::map<TCoords<FCoords>, DescriptionIndex> triangles;
921 	const FCoords f = get_position();
922 	FCoords tln, ln, trn;
923 	game.map().get_tln(f, &tln);
924 	game.map().get_trn(f, &trn);
925 	game.map().get_ln(f, &ln);
926 
927 	DescriptionIndex di =
928 	   world.get_terrain_index(world.terrain_descr(f.field->terrain_r()).enhancement());
929 	if (di != INVALID_INDEX) {
930 		triangles.emplace(std::make_pair(TCoords<FCoords>(f, TriangleIndex::R), di));
931 	}
932 	di = world.get_terrain_index(world.terrain_descr(f.field->terrain_d()).enhancement());
933 	if (di != INVALID_INDEX) {
934 		triangles.emplace(std::make_pair(TCoords<FCoords>(f, TriangleIndex::D), di));
935 	}
936 	di = world.get_terrain_index(world.terrain_descr(tln.field->terrain_r()).enhancement());
937 	if (di != INVALID_INDEX) {
938 		triangles.emplace(std::make_pair(TCoords<FCoords>(tln, TriangleIndex::R), di));
939 	}
940 	di = world.get_terrain_index(world.terrain_descr(tln.field->terrain_d()).enhancement());
941 	if (di != INVALID_INDEX) {
942 		triangles.emplace(std::make_pair(TCoords<FCoords>(tln, TriangleIndex::D), di));
943 	}
944 	di = world.get_terrain_index(world.terrain_descr(ln.field->terrain_r()).enhancement());
945 	if (di != INVALID_INDEX) {
946 		triangles.emplace(std::make_pair(TCoords<FCoords>(ln, TriangleIndex::R), di));
947 	}
948 	di = world.get_terrain_index(world.terrain_descr(trn.field->terrain_d()).enhancement());
949 	if (di != INVALID_INDEX) {
950 		triangles.emplace(std::make_pair(TCoords<FCoords>(trn, TriangleIndex::D), di));
951 	}
952 
953 	if (triangles.empty()) {
954 		send_signal(game, "fail");
955 		pop_task(game);
956 		return false;
957 	}
958 	assert(game.mutable_map());
959 	auto it = triangles.begin();
960 	for (size_t rand = game.logic_rand() % triangles.size(); rand > 0; --rand)
961 		++it;
962 	game.mutable_map()->change_terrain(game, it->first, it->second);
963 	++state.ivar1;
964 	schedule_act(game, 10);
965 	return true;
966 }
967 
968 /**
969  * buildferry
970  *
971  * Creates a new instance of the ferry the worker's
972  * tribe uses and adds it to the appropriate fleet.
973  *
974  */
run_buildferry(Game & game,State & state,const Action &)975 bool Worker::run_buildferry(Game& game, State& state, const Action&) {
976 	game.create_ferry(get_position(), owner_);
977 	++state.ivar1;
978 	schedule_act(game, 10);
979 	return true;
980 }
981 
982 /**
983  * Simply remove the currently selected object - make no fuss about it.
984  */
run_removeobject(Game & game,State & state,const Action &)985 bool Worker::run_removeobject(Game& game, State& state, const Action&) {
986 	if (MapObject* const obj = state.objvar1.get(game)) {
987 		obj->remove(game);
988 		state.objvar1 = nullptr;
989 	}
990 
991 	++state.ivar1;
992 	schedule_act(game, 10);
993 	return true;
994 }
995 
996 /**
997  * repeatsearch=\<repeat #\> \<radius\> \<subcommand\>
998  *
999  * Walk around the starting point randomly within a certain radius, and
1000  * execute the subcommand for some of the fields.
1001  *
1002  * iparam1 = maximum repeat #
1003  * iparam2 = radius
1004  * sparam1 = subcommand
1005  */
run_repeatsearch(Game & game,State & state,const Action & action)1006 bool Worker::run_repeatsearch(Game& game, State& state, const Action& action) {
1007 	molog("  Start Repeat Search (%i attempts, %i radius -> %s)\n", action.iparam1, action.iparam2,
1008 	      action.sparam1.c_str());
1009 
1010 	++state.ivar1;
1011 	start_task_geologist(game, action.iparam1, action.iparam2, action.sparam1);
1012 	return true;
1013 }
1014 
1015 /**
1016  * Check resources at the current position, and plant a marker object when
1017  * possible.
1018  */
run_findresources(Game & game,State & state,const Action &)1019 bool Worker::run_findresources(Game& game, State& state, const Action&) {
1020 	const FCoords position = game.map().get_fcoords(get_position());
1021 	BaseImmovable const* const imm = position.field->get_immovable();
1022 	const World& world = game.world();
1023 
1024 	if (!(imm && imm->get_size() > BaseImmovable::NONE)) {
1025 
1026 		const ResourceDescription* const rdescr = world.get_resource(position.field->get_resources());
1027 		const TribeDescr& t = owner().tribe();
1028 		const Immovable& ri = game.create_immovable(
1029 		   position,
1030 		   t.get_resource_indicator(
1031 		      rdescr, (rdescr && rdescr->detectable()) ? position.field->get_resources_amount() : 0),
1032 		   MapObjectDescr::OwnerType::kTribe, get_owner());
1033 
1034 		// Geologist also sends a message notifying the player
1035 		// TODO(GunChleoc): We keep formatting this even when timeout has not elapsed
1036 		if (rdescr && rdescr->detectable() && position.field->get_resources_amount()) {
1037 			const std::string rt_description = as_mapobject_message(
1038 			   ri.descr().name(), g_gr->images().get(rdescr->representative_image())->width(),
1039 			   _("A geologist found resources."));
1040 
1041 			//  We should add a message to the player's message queue - but only,
1042 			//  if there is not already a similar one in list.
1043 			get_owner()->add_message_with_timeout(
1044 			   game, std::unique_ptr<Message>(new Message(
1045 			            Message::Type::kGeologists, game.get_gametime(), rdescr->descname(),
1046 			            rdescr->representative_image(), ri.descr().descname(), rt_description,
1047 			            position, serial_, rdescr->name())),
1048 			   rdescr->timeout_ms(), rdescr->timeout_radius());
1049 		}
1050 	}
1051 
1052 	++state.ivar1;
1053 	return false;
1054 }
1055 
1056 /**
1057  * Demand from the g_sh to play a certain sound effect.
1058  * Whether the effect actually gets played is decided only by the sound server.
1059  */
run_playsound(Game & game,State & state,const Action & action)1060 bool Worker::run_playsound(Game& game, State& state, const Action& action) {
1061 	Notifications::publish(
1062 	   NoteSound(SoundType::kAmbient, action.iparam2, get_position(), action.iparam1));
1063 
1064 	++state.ivar1;
1065 	schedule_act(game, 10);
1066 	return true;
1067 }
1068 
1069 /**
1070  * If we are currently carrying some ware ware, hand it off to the currently
1071  * selected immovable (\ref objvar1) for construction.
1072  */
run_construct(Game & game,State & state,const Action &)1073 bool Worker::run_construct(Game& game, State& state, const Action& /* action */) {
1074 	Immovable* imm = dynamic_cast<Immovable*>(state.objvar1.get(game));
1075 	if (!imm) {
1076 		molog("run_construct: no objvar1 immovable set");
1077 		send_signal(game, "fail");
1078 		pop_task(game);
1079 		return true;
1080 	}
1081 
1082 	WareInstance* ware = get_carried_ware(game);
1083 	if (!ware) {
1084 		molog("run_construct: no ware being carried");
1085 		send_signal(game, "fail");
1086 		pop_task(game);
1087 		return true;
1088 	}
1089 
1090 	DescriptionIndex wareindex = ware->descr_index();
1091 	if (!imm->construct_ware(game, wareindex)) {
1092 		molog("run_construct: construct_ware failed");
1093 		send_signal(game, "fail");
1094 		pop_task(game);
1095 		return true;
1096 	}
1097 
1098 	// Update consumption statistic
1099 	get_owner()->ware_consumed(wareindex, 1);
1100 
1101 	ware = fetch_carried_ware(game);
1102 	ware->remove(game);
1103 
1104 	++state.ivar1;
1105 	schedule_act(game, 10);
1106 	return true;
1107 }
1108 
Worker(const WorkerDescr & worker_descr)1109 Worker::Worker(const WorkerDescr& worker_descr)
1110    : Bob(worker_descr),
1111      worker_economy_(nullptr),
1112      ware_economy_(nullptr),
1113      supply_(nullptr),
1114      transfer_(nullptr),
1115      current_exp_(0) {
1116 }
1117 
~Worker()1118 Worker::~Worker() {
1119 	assert(!location_.is_set());
1120 	assert(!transfer_);
1121 }
1122 
1123 /// Log basic information.
log_general_info(const EditorGameBase & egbase) const1124 void Worker::log_general_info(const EditorGameBase& egbase) const {
1125 	Bob::log_general_info(egbase);
1126 
1127 	if (upcast(PlayerImmovable, loc, location_.get(egbase))) {
1128 		FORMAT_WARNINGS_OFF
1129 		molog("* Owner: (%p)\n", &loc->owner());
1130 		FORMAT_WARNINGS_ON
1131 		molog("** Owner (plrnr): %i\n", loc->owner().player_number());
1132 		FORMAT_WARNINGS_OFF
1133 		molog("* WorkerEconomy: %p\n", loc->get_economy(wwWORKER));
1134 		molog("* WareEconomy: %p\n", loc->get_economy(wwWARE));
1135 		FORMAT_WARNINGS_ON
1136 	}
1137 
1138 	PlayerImmovable* imm = location_.get(egbase);
1139 	molog("location: %u\n", imm ? imm->serial() : 0);
1140 	FORMAT_WARNINGS_OFF
1141 	molog("WorkerEconomy: %p\n", worker_economy_);
1142 	molog("WareEconomy: %p\n", ware_economy_);
1143 	molog("transfer: %p\n", transfer_);
1144 	FORMAT_WARNINGS_ON
1145 
1146 	if (upcast(WareInstance, ware, carried_ware_.get(egbase))) {
1147 		molog("* carried_ware->get_ware() (id): %i\n", ware->descr_index());
1148 		FORMAT_WARNINGS_OFF
1149 		molog("* carried_ware->get_economy() (): %p\n", ware->get_economy());
1150 		FORMAT_WARNINGS_ON
1151 	}
1152 
1153 	molog("current_exp: %i / %i\n", current_exp_, descr().get_needed_experience());
1154 
1155 	FORMAT_WARNINGS_OFF
1156 	molog("supply: %p\n", supply_);
1157 	FORMAT_WARNINGS_ON
1158 }
1159 
1160 /**
1161  * Change the location. This should be called in the following situations:
1162  * \li worker creation (usually, location is a warehouse)
1163  * \li worker moves along a route (location is a road and finally building)
1164  * \li current location is destroyed (building burnt down etc...)
1165  */
set_location(PlayerImmovable * const location)1166 void Worker::set_location(PlayerImmovable* const location) {
1167 	assert(!location || ObjectPointer(location).get(owner().egbase()));
1168 
1169 	PlayerImmovable* const old_location = get_location(owner().egbase());
1170 	if (old_location == location)
1171 		return;
1172 
1173 	if (old_location) {
1174 		// Note: even though we have an old location, economy_ may be zero
1175 		// (old_location got deleted)
1176 		old_location->remove_worker(*this);
1177 	} else {
1178 		if (!is_shipping()) {
1179 			assert(!ware_economy_);
1180 			assert(!worker_economy_);
1181 		}
1182 	}
1183 
1184 	location_ = location;
1185 
1186 	if (location) {
1187 		Economy* const eco_wo = location->get_economy(wwWORKER);
1188 		Economy* const eco_wa = location->get_economy(wwWARE);
1189 
1190 		if (!worker_economy_ || (descr().type() == MapObjectType::SOLDIER)) {
1191 			set_economy(eco_wo, wwWORKER);
1192 		} else if (worker_economy_ != eco_wo) {
1193 			throw wexception("Worker::set_location changes worker_economy, but worker is no soldier");
1194 		}
1195 		if (!ware_economy_ || (descr().type() == MapObjectType::SOLDIER)) {
1196 			set_economy(eco_wa, wwWARE);
1197 		} else if (ware_economy_ != eco_wa) {
1198 			throw wexception("Worker::set_location changes ware_economy, but worker is no soldier");
1199 		}
1200 		location->add_worker(*this);
1201 	} else {
1202 		if (!is_shipping()) {
1203 			// Our location has been destroyed, we are now fugitives.
1204 			// Interrupt whatever we've been doing.
1205 			set_economy(nullptr, wwWARE);
1206 			set_economy(nullptr, wwWORKER);
1207 
1208 			EditorGameBase& egbase = get_owner()->egbase();
1209 			if (upcast(Game, game, &egbase)) {
1210 				send_signal(*game, "location");
1211 			}
1212 		}
1213 	}
1214 }
1215 
1216 /**
1217  * Change the worker's current economy. This is called:
1218  * \li by set_location() when appropriate
1219  * \li by the current location, when the location's economy changes
1220  */
set_economy(Economy * const economy,WareWorker type)1221 void Worker::set_economy(Economy* const economy, WareWorker type) {
1222 	Economy* old = get_economy(type);
1223 	if (economy == old) {
1224 		return;
1225 	}
1226 
1227 	switch (type) {
1228 	case wwWARE: {
1229 		ware_economy_ = economy;
1230 		if (WareInstance* const ware = get_carried_ware(get_owner()->egbase())) {
1231 			ware->set_economy(ware_economy_);
1232 		}
1233 	} break;
1234 	case wwWORKER: {
1235 		worker_economy_ = economy;
1236 		if (old) {
1237 			old->remove_wares_or_workers(owner().tribe().worker_index(descr().name().c_str()), 1);
1238 		}
1239 		if (supply_) {
1240 			supply_->set_economy(worker_economy_);
1241 		}
1242 		if (worker_economy_) {
1243 			worker_economy_->add_wares_or_workers(
1244 			   owner().tribe().worker_index(descr().name().c_str()), 1, ware_economy_);
1245 		}
1246 	} break;
1247 	}
1248 }
1249 
1250 /**
1251  * Initialize the worker
1252  */
init(EditorGameBase & egbase)1253 bool Worker::init(EditorGameBase& egbase) {
1254 	Bob::init(egbase);
1255 
1256 	// a worker should always start out at a fixed location
1257 	// (this assert is not longer true for save games. Where it lives
1258 	// is unknown to this worker till he is initialized
1259 	//  assert(get_location(egbase));
1260 
1261 	if (upcast(Game, game, &egbase))
1262 		create_needed_experience(*game);
1263 	return true;
1264 }
1265 
1266 /**
1267  * Remove the worker.
1268  */
cleanup(EditorGameBase & egbase)1269 void Worker::cleanup(EditorGameBase& egbase) {
1270 	WareInstance* const ware = get_carried_ware(egbase);
1271 
1272 	if (supply_) {
1273 		delete supply_;
1274 		supply_ = nullptr;
1275 	}
1276 
1277 	if (ware)
1278 		if (egbase.objects().object_still_available(ware))
1279 			ware->destroy(egbase);
1280 
1281 	// We are destroyed, but we were maybe idling
1282 	// or doing something else. Get Location might
1283 	// init a gowarehouse task or something and this results
1284 	// in a dirty stack. Nono, we do not want to end like this
1285 	if (upcast(Game, game, &egbase))
1286 		reset_tasks(*game);
1287 
1288 	if (get_location(egbase))
1289 		set_location(nullptr);
1290 
1291 	set_economy(nullptr, wwWARE);
1292 	set_economy(nullptr, wwWORKER);
1293 
1294 	Bob::cleanup(egbase);
1295 }
1296 
1297 /**
1298  * Set the ware we carry.
1299  * If we carry an ware right now, it will be destroyed (see
1300  * fetch_carried_ware()).
1301  */
set_carried_ware(EditorGameBase & egbase,WareInstance * const ware)1302 void Worker::set_carried_ware(EditorGameBase& egbase, WareInstance* const ware) {
1303 	if (WareInstance* const oldware = get_carried_ware(egbase)) {
1304 		oldware->cleanup(egbase);
1305 		delete oldware;
1306 	}
1307 
1308 	carried_ware_ = ware;
1309 	ware->set_location(egbase, this);
1310 	if (upcast(Game, game, &egbase))
1311 		ware->update(*game);
1312 }
1313 
1314 /**
1315  * Stop carrying the current ware, and return a pointer to it.
1316  */
fetch_carried_ware(EditorGameBase & game)1317 WareInstance* Worker::fetch_carried_ware(EditorGameBase& game) {
1318 	WareInstance* const ware = get_carried_ware(game);
1319 
1320 	if (ware) {
1321 		ware->set_location(game, nullptr);
1322 		carried_ware_ = nullptr;
1323 	}
1324 
1325 	return ware;
1326 }
1327 
1328 /**
1329  * Schedule an immediate CMD_INCORPORATE, which will integrate this worker into
1330  * the warehouse he is standing on.
1331  */
schedule_incorporate(Game & game)1332 void Worker::schedule_incorporate(Game& game) {
1333 	game.cmdqueue().enqueue(new CmdIncorporate(game.get_gametime(), this));
1334 	return skip_act();
1335 }
1336 
1337 /**
1338  * Incorporate the worker into the warehouse it's standing on immediately.
1339  */
incorporate(Game & game)1340 void Worker::incorporate(Game& game) {
1341 	if (upcast(Warehouse, wh, get_location(game))) {
1342 		wh->incorporate_worker(game, this);
1343 		return;
1344 	}
1345 
1346 	// our location has been deleted from under us
1347 	send_signal(game, "fail");
1348 }
1349 
1350 /**
1351  * Calculate needed experience.
1352  *
1353  * This sets the needed experience on a value between max and min
1354  */
create_needed_experience(Game &)1355 void Worker::create_needed_experience(Game& /* game */) {
1356 	if (descr().get_needed_experience() == INVALID_INDEX) {
1357 		current_exp_ = INVALID_INDEX;
1358 		return;
1359 	}
1360 
1361 	current_exp_ = 0;
1362 }
1363 
1364 /**
1365  * Gain experience
1366  *
1367  * This function increases the experience
1368  * of the worker by one, if he reaches
1369  * needed_experience he levels
1370  */
gain_experience(Game & game)1371 DescriptionIndex Worker::gain_experience(Game& game) {
1372 	return (descr().get_needed_experience() == INVALID_INDEX ||
1373 	        ++current_exp_ < descr().get_needed_experience()) ?
1374 	          INVALID_INDEX :
1375 	          level(game);
1376 }
1377 
1378 /**
1379  * Level this worker to the next higher level. this includes creating a
1380  * new worker with his propertys and removing this worker
1381  */
level(Game & game)1382 DescriptionIndex Worker::level(Game& game) {
1383 
1384 	// We do not really remove this worker, all we do
1385 	// is to overwrite his description with the new one and to
1386 	// reset his needed experience. Congratulations to promotion!
1387 	// This silently expects that the new worker is the same type as the old
1388 	// worker and can fullfill the same jobs (which should be given in all
1389 	// circumstances)
1390 	assert(descr().becomes() != INVALID_INDEX);
1391 	const TribeDescr& t = owner().tribe();
1392 	DescriptionIndex const old_index = t.worker_index(descr().name());
1393 	DescriptionIndex const new_index = descr().becomes();
1394 	descr_ = t.get_worker_descr(new_index);
1395 	assert(t.has_worker(new_index));
1396 
1397 	// Inform the economy, that something has changed
1398 	worker_economy_->remove_wares_or_workers(old_index, 1);
1399 	worker_economy_->add_wares_or_workers(new_index, 1, ware_economy_);
1400 
1401 	create_needed_experience(game);
1402 	return old_index;  //  So that the caller knows what to replace him with.
1403 }
1404 
1405 /**
1406  * Set a fallback task.
1407  */
init_auto_task(Game & game)1408 void Worker::init_auto_task(Game& game) {
1409 	if (PlayerImmovable* location = get_location(game)) {
1410 		if (get_economy(wwWORKER)->warehouses().size() ||
1411 		    location->descr().type() >= MapObjectType::BUILDING)
1412 			return start_task_gowarehouse(game);
1413 
1414 		set_location(nullptr);
1415 	}
1416 
1417 	molog("init_auto_task: become fugitive\n");
1418 
1419 	return start_task_fugitive(game);
1420 }
1421 
1422 /**
1423  * Follow the given transfer.
1424  *
1425  * Signal "cancel" to cancel the transfer.
1426  */
1427 const Bob::Task Worker::taskTransfer = {"transfer", static_cast<Bob::Ptr>(&Worker::transfer_update),
1428                                         nullptr, static_cast<Bob::Ptr>(&Worker::transfer_pop),
1429                                         false};
1430 
1431 /**
1432  * Tell the worker to follow the Transfer
1433  */
start_task_transfer(Game & game,Transfer * t)1434 void Worker::start_task_transfer(Game& game, Transfer* t) {
1435 	// Hackish override for receiving transfers during gowarehouse,
1436 	// and to correctly handle the stack during loading of games
1437 	// (in that case, the transfer task already exists on the stack
1438 	// when this is called).
1439 	if (get_state(taskGowarehouse) || get_state(taskTransfer)) {
1440 		assert(!transfer_);
1441 
1442 		transfer_ = t;
1443 		send_signal(game, "transfer");
1444 	} else {  //  just start a normal transfer
1445 		push_task(game, taskTransfer);
1446 		transfer_ = t;
1447 	}
1448 }
1449 
transfer_pop(Game &,State &)1450 void Worker::transfer_pop(Game& /* game */, State& /* state */) {
1451 	if (transfer_) {
1452 		transfer_->has_failed();
1453 		transfer_ = nullptr;
1454 	}
1455 }
1456 
transfer_update(Game & game,State &)1457 void Worker::transfer_update(Game& game, State& /* state */) {
1458 	const Map& map = game.map();
1459 	PlayerImmovable* location = get_location(game);
1460 
1461 	// We expect to always have a location at this point,
1462 	// but this assumption may fail when loading a corrupted savegame.
1463 	if (!location) {
1464 		send_signal(game, "location");
1465 		return pop_task(game);
1466 	}
1467 
1468 	// The request is no longer valid, the task has failed
1469 	if (!transfer_) {
1470 		molog("[transfer]: Fail (without transfer)\n");
1471 
1472 		send_signal(game, "fail");
1473 		return pop_task(game);
1474 	}
1475 
1476 	// Signal handling
1477 	const std::string& signal = get_signal();
1478 
1479 	if (signal.size()) {
1480 		// The caller requested a route update, or the previously calculated route
1481 		// failed.
1482 		// We will recalculate the route on the next update().
1483 		if (signal == "road" || signal == "fail" || signal == "transfer" || signal == "wakeup") {
1484 			molog("[transfer]: Got signal '%s' -> recalculate\n", signal.c_str());
1485 
1486 			signal_handled();
1487 		} else if (signal == "blocked") {
1488 			molog("[transfer]: Blocked by a battle\n");
1489 
1490 			signal_handled();
1491 			return start_task_idle(game, descr().get_animation("idle", this), 500);
1492 		} else {
1493 			molog("[transfer]: Cancel due to signal '%s'\n", signal.c_str());
1494 			return pop_task(game);
1495 		}
1496 	}
1497 
1498 	// If our location is a building, our position may be somewhere else:
1499 	// We may be on the building's flag for a fetch_from_flag or dropoff task.
1500 	// We may also be somewhere else entirely (e.g. lumberjack, soldier).
1501 	// Similarly for flags.
1502 	if (upcast(Building, building, location)) {
1503 		if (building->get_position() != get_position())
1504 			return start_task_leavebuilding(game, true);
1505 	} else if (upcast(Flag, flag, location)) {
1506 		BaseImmovable* const position = map[get_position()].get_immovable();
1507 
1508 		if (position != flag) {
1509 			if (position == flag->get_building()) {
1510 				location = flag->get_building();
1511 				set_location(location);
1512 			} else
1513 				return set_location(nullptr);
1514 		}
1515 	}
1516 
1517 	// Figure out where to go
1518 	bool success;
1519 	PlayerImmovable* const nextstep = transfer_->get_next_step(location, success);
1520 
1521 	if (!nextstep) {
1522 		Transfer* const t = transfer_;
1523 
1524 		transfer_ = nullptr;
1525 
1526 		if (success) {
1527 			pop_task(game);
1528 
1529 			t->has_finished();
1530 		} else {
1531 			send_signal(game, "fail");
1532 			pop_task(game);
1533 
1534 			t->has_failed();
1535 		}
1536 		return;
1537 	}
1538 
1539 	// Initiate the next step
1540 	if (upcast(Building, building, location)) {
1541 		if (&building->base_flag() != nextstep) {
1542 			if (upcast(Warehouse, warehouse, building)) {
1543 				if (warehouse->get_portdock() == nextstep)
1544 					return start_task_shipping(game, warehouse->get_portdock());
1545 			}
1546 
1547 			throw wexception(
1548 			   "MO(%u): [transfer]: in building, nextstep is not building's flag", serial());
1549 		}
1550 
1551 		return start_task_leavebuilding(game, true);
1552 	} else if (upcast(Flag, flag, location)) {
1553 		if (upcast(Building, nextbuild, nextstep)) {  //  Flag to Building
1554 			if (&nextbuild->base_flag() != location)
1555 				throw wexception(
1556 				   "MO(%u): [transfer]: next step is building, but we are nowhere near", serial());
1557 
1558 			return start_task_move(
1559 			   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
1560 		} else if (upcast(Flag, nextflag, nextstep)) {  //  Flag to Flag
1561 			Road& road = *flag->get_road(*nextflag);
1562 
1563 			Path path(road.get_path());
1564 
1565 			if (nextstep != &road.get_flag(RoadBase::FlagEnd)) {
1566 				path.reverse();
1567 			}
1568 
1569 			molog("[transfer]: starting task [movepath] and setting location to road %u\n",
1570 			      road.serial());
1571 			start_task_movepath(game, path, descr().get_right_walk_anims(does_carry_ware(), this));
1572 			set_location(&road);
1573 		} else if (upcast(RoadBase, road, nextstep)) {  //  Flag to Road
1574 			if (&road->get_flag(RoadBase::FlagStart) != location &&
1575 			    &road->get_flag(RoadBase::FlagEnd) != location) {
1576 				throw wexception(
1577 				   "MO(%u): [transfer]: nextstep is road, but we are nowhere near", serial());
1578 			}
1579 			molog("[transfer]: set location to road %u\n", road->serial());
1580 			set_location(road);
1581 			set_animation(game, descr().get_animation("idle", this));
1582 			schedule_act(game, 10);  //  wait a little
1583 		} else
1584 			throw wexception(
1585 			   "MO(%u): [transfer]: flag to bad nextstep %u", serial(), nextstep->serial());
1586 	} else if (upcast(RoadBase, road, location)) {
1587 		// Road to Flag
1588 		if (nextstep->descr().type() == MapObjectType::FLAG) {
1589 			const Path& path = road->get_path();
1590 			int32_t const index =
1591 			   nextstep == &road->get_flag(RoadBase::FlagStart) ?
1592 			      0 :
1593 			      nextstep == &road->get_flag(RoadBase::FlagEnd) ? path.get_nsteps() : -1;
1594 
1595 			if (index >= 0) {
1596 				if (start_task_movepath(
1597 				       game, path, index, descr().get_right_walk_anims(does_carry_ware(), this))) {
1598 					molog("[transfer]: from road %u to flag %u\n", road->serial(), nextstep->serial());
1599 					return;
1600 				}
1601 			} else if (nextstep != map[get_position()].get_immovable())
1602 				throw wexception(
1603 				   "MO(%u): [transfer]: road to flag, but flag is nowhere near", serial());
1604 
1605 			set_location(dynamic_cast<Flag*>(nextstep));
1606 			set_animation(game, descr().get_animation("idle", this));
1607 			schedule_act(game, 10);  //  wait a little
1608 		} else
1609 			throw wexception(
1610 			   "MO(%u): [transfer]: from road to bad nextstep %u", serial(), nextstep->serial());
1611 	} else
1612 		// Scan-build reports Called C++ object pointer is null here.
1613 		// This is a false positive.
1614 		// See https://bugs.launchpad.net/widelands/+bug/1198918
1615 		throw wexception("MO(%u): location %u has bad type", serial(), location->serial());
1616 }
1617 
1618 /**
1619  * Called by transport code when the transfer has been cancelled & destroyed.
1620  */
cancel_task_transfer(Game & game)1621 void Worker::cancel_task_transfer(Game& game) {
1622 	transfer_ = nullptr;
1623 	send_signal(game, "cancel");
1624 }
1625 
1626 /**
1627  * Sleep while the shipping code in @ref PortDock and @ref Ship handles us.
1628  */
1629 const Bob::Task Worker::taskShipping = {"shipping", static_cast<Bob::Ptr>(&Worker::shipping_update),
1630                                         nullptr, static_cast<Bob::Ptr>(&Worker::shipping_pop),
1631                                         true};
1632 
1633 /**
1634  * Start the shipping task. If pd != nullptr, add us as a shipping item. We
1635  * could be an expedition worker though, so we will not be a shipping item
1636  * though.
1637  *
1638  * ivar1 = end shipping?
1639  */
start_task_shipping(Game & game,PortDock * pd)1640 void Worker::start_task_shipping(Game& game, PortDock* pd) {
1641 	push_task(game, taskShipping);
1642 	top_state().ivar1 = 0;
1643 	if (pd)
1644 		pd->add_shippingitem(game, *this);
1645 }
1646 
1647 /**
1648  * Trigger the end of the shipping task.
1649  *
1650  * @note the worker must be in a @ref Warehouse location
1651  */
end_shipping(Game & game)1652 void Worker::end_shipping(Game& game) {
1653 	if (State* state = get_state(taskShipping)) {
1654 		state->ivar1 = 1;
1655 		send_signal(game, "endshipping");
1656 	}
1657 }
1658 
1659 /**
1660  * Whether we are currently being handled by the shipping code.
1661  */
is_shipping()1662 bool Worker::is_shipping() {
1663 	return get_state(taskShipping);
1664 }
1665 
shipping_pop(Game & game,State &)1666 void Worker::shipping_pop(Game& game, State& /* state */) {
1667 	// Defense against unorderly cleanup via reset_tasks
1668 	if (!get_location(game)) {
1669 		set_economy(nullptr, wwWARE);
1670 		set_economy(nullptr, wwWORKER);
1671 	}
1672 }
1673 
shipping_update(Game & game,State & state)1674 void Worker::shipping_update(Game& game, State& state) {
1675 	PlayerImmovable* location = get_location(game);
1676 
1677 	// Signal handling
1678 	const std::string& signal = get_signal();
1679 
1680 	if (signal.size()) {
1681 		if (signal == "endshipping") {
1682 			signal_handled();
1683 			if (!dynamic_cast<Warehouse*>(location)) {
1684 				molog("shipping_update: received signal 'endshipping' while not in warehouse!\n");
1685 				pop_task(game);
1686 				return;
1687 			}
1688 		}
1689 		if (signal == "transfer" || signal == "wakeup")
1690 			signal_handled();
1691 	}
1692 
1693 	if (location || state.ivar1) {
1694 		if (upcast(PortDock, pd, location)) {
1695 			pd->update_shippingitem(game, *this);
1696 		} else {
1697 			return pop_task(game);
1698 		}
1699 	}
1700 
1701 	start_task_idle(game, 0, -1);
1702 }
1703 
1704 /**
1705  * Endless loop, in which the worker calls the owning building's
1706  * get_building_work() function to initiate subtasks.
1707  * The signal "update" is used to wake the worker up after a sleeping time
1708  * (initiated by a false return value from get_building_work()).
1709  *
1710  * ivar1 - 0: no task has failed; 1: currently in buildingwork;
1711  *         2: signal failure of buildingwork
1712  * ivar2 - whether the worker is to be evicted
1713  */
1714 const Bob::Task Worker::taskBuildingwork = {
1715    "buildingwork", static_cast<Bob::Ptr>(&Worker::buildingwork_update), nullptr, nullptr, true};
1716 
1717 /**
1718  * Begin work at a building.
1719  */
start_task_buildingwork(Game & game)1720 void Worker::start_task_buildingwork(Game& game) {
1721 	push_task(game, taskBuildingwork);
1722 	State& state = top_state();
1723 	state.ivar1 = 0;
1724 }
1725 
buildingwork_update(Game & game,State & state)1726 void Worker::buildingwork_update(Game& game, State& state) {
1727 	// Reset any signals that are not related to location
1728 	std::string signal = get_signal();
1729 	signal_handled();
1730 
1731 	upcast(Building, building, get_location(game));
1732 
1733 	if (signal == "evict") {
1734 		if (building) {
1735 			// If the building was working, we do not tell it to cancel – it'll notice by itself soon –
1736 			// but we already change the animation so it won't look strange
1737 			building->start_animation(game, building->descr().get_unoccupied_animation());
1738 		}
1739 		return pop_task(game);
1740 	}
1741 
1742 	if (state.ivar1 == 1)
1743 		state.ivar1 = (signal == "fail") * 2;
1744 
1745 	// Return to building, if necessary
1746 	if (!building)
1747 		return pop_task(game);
1748 
1749 	if (game.map().get_immovable(get_position()) != building)
1750 		return start_task_return(game, false);  //  do not drop ware
1751 
1752 	// Get the new job
1753 	bool const success = state.ivar1 != 2;
1754 
1755 	// Set this *before* calling to get_building_work, because the
1756 	// state pointer might become invalid
1757 	state.ivar1 = 1;
1758 
1759 	if (!building->get_building_work(game, *this, success)) {
1760 		set_animation(game, 0);
1761 		return skip_act();
1762 	}
1763 }
1764 
1765 /**
1766  * Wake up the buildingwork task if it was sleeping.
1767  * Otherwise, the buildingwork task will update as soon as the previous task
1768  * is finished.
1769  */
update_task_buildingwork(Game & game)1770 void Worker::update_task_buildingwork(Game& game) {
1771 	if (top_state().task == &taskBuildingwork)
1772 		send_signal(game, "update");
1773 }
1774 
1775 // The task when a worker is part of the caravan that is trading items.
1776 const Bob::Task Worker::taskCarryTradeItem = {
1777    "carry_trade_item", static_cast<Bob::Ptr>(&Worker::carry_trade_item_update), nullptr, nullptr,
1778    true};
1779 
start_task_carry_trade_item(Game & game,const int trade_id,ObjectPointer other_market)1780 void Worker::start_task_carry_trade_item(Game& game,
1781                                          const int trade_id,
1782                                          ObjectPointer other_market) {
1783 	push_task(game, taskCarryTradeItem);
1784 	auto& state = top_state();
1785 	state.ivar1 = 0;
1786 	state.ivar2 = trade_id;
1787 	state.objvar1 = other_market;
1788 }
1789 
1790 // This is a state machine: leave building, go to the other market, drop off
1791 // wares, and return.
carry_trade_item_update(Game & game,State & state)1792 void Worker::carry_trade_item_update(Game& game, State& state) {
1793 	// Reset any signals that are not related to location
1794 	std::string signal = get_signal();
1795 	signal_handled();
1796 	if (!signal.empty()) {
1797 		// TODO(sirver,trading): Remove once signals are correctly handled.
1798 		log("carry_trade_item_update: signal received: %s\n", signal.c_str());
1799 	}
1800 	if (signal == "evict") {
1801 		return pop_task(game);
1802 	}
1803 
1804 	// First of all, make sure we're outside
1805 	if (state.ivar1 == 0) {
1806 		start_task_leavebuilding(game, false);
1807 		++state.ivar1;
1808 		return;
1809 	}
1810 
1811 	auto* other_market = dynamic_cast<Market*>(state.objvar1.get(game));
1812 	if (state.ivar1 == 1) {
1813 		// Arrived on site. Move to the building and advance our state.
1814 		if (other_market->base_flag().get_position() == get_position()) {
1815 			++state.ivar1;
1816 			return start_task_move(
1817 			   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
1818 		}
1819 
1820 		// Otherwise continue making progress towards the other market.
1821 		if (!start_task_movepath(game, other_market->base_flag().get_position(), 5,
1822 		                         descr().get_right_walk_anims(does_carry_ware(), this))) {
1823 			molog("carry_trade_item_update: Could not move to other flag.\n");
1824 			// TODO(sirver,trading): something needs to happen here.
1825 		}
1826 		return;
1827 	}
1828 
1829 	if (state.ivar1 == 2) {
1830 		WareInstance* const ware = fetch_carried_ware(game);
1831 		other_market->traded_ware_arrived(state.ivar2, ware->descr_index(), &game);
1832 		ware->remove(game);
1833 		++state.ivar1;
1834 		start_task_move(game, WALK_SE, descr().get_right_walk_anims(does_carry_ware(), this), true);
1835 		return;
1836 	}
1837 
1838 	if (state.ivar1 == 3) {
1839 		++state.ivar1;
1840 		start_task_return(game, false);
1841 		return;
1842 	}
1843 
1844 	if (state.ivar1 == 4) {
1845 		pop_task(game);
1846 		start_task_idle(game, 0, -1);
1847 		dynamic_cast<Market*>(get_location(game))->try_launching_batch(&game);
1848 		return;
1849 	}
1850 	NEVER_HERE();
1851 }
1852 
update_task_carry_trade_item(Game & game)1853 void Worker::update_task_carry_trade_item(Game& game) {
1854 	if (top_state().task == &taskCarryTradeItem)
1855 		send_signal(game, "update");
1856 }
1857 
1858 /**
1859  * Evict the worker from its current building.
1860  */
evict(Game & game)1861 void Worker::evict(Game& game) {
1862 	if (is_evict_allowed()) {
1863 		send_signal(game, "evict");
1864 	}
1865 }
1866 
is_evict_allowed()1867 bool Worker::is_evict_allowed() {
1868 	return true;
1869 }
1870 
1871 /**
1872  * Return to our owning building.
1873  * If dropware (ivar1) is true, we'll drop our carried ware (if any) on the
1874  * building's flag, if possible.
1875  * Blocks all signals except for "location".
1876  */
1877 const Bob::Task Worker::taskReturn = {
1878    "return", static_cast<Bob::Ptr>(&Worker::return_update), nullptr, nullptr, true};
1879 
1880 /**
1881  * Return to our owning building.
1882  */
start_task_return(Game & game,bool const dropware)1883 void Worker::start_task_return(Game& game, bool const dropware) {
1884 	PlayerImmovable* const location = get_location(game);
1885 
1886 	if (!location || location->descr().type() < MapObjectType::BUILDING)
1887 		throw wexception("MO(%u): start_task_return(): not owned by building", serial());
1888 
1889 	push_task(game, taskReturn);
1890 	top_state().ivar1 = dropware ? 1 : 0;
1891 }
1892 
return_update(Game & game,State & state)1893 void Worker::return_update(Game& game, State& state) {
1894 	std::string signal = get_signal();
1895 
1896 	if (signal == "location") {
1897 		molog("[return]: Interrupted by signal '%s'\n", signal.c_str());
1898 		return pop_task(game);
1899 	}
1900 
1901 	signal_handled();
1902 
1903 	Building* location = dynamic_cast<Building*>(get_location(game));
1904 
1905 	if (!location) {
1906 		// Usually, this should be caught via the "location" signal above.
1907 		// However, in certain cases, e.g. for a soldier during battle,
1908 		// the location may be overwritten by a different signal while
1909 		// walking home.
1910 		molog("[return]: Our location disappeared from under us\n");
1911 		return pop_task(game);
1912 	}
1913 
1914 	if (BaseImmovable* const pos = game.map().get_immovable(get_position())) {
1915 		if (pos == location) {
1916 			set_animation(game, 0);
1917 			return pop_task(game);
1918 		}
1919 
1920 		if (upcast(Flag, flag, pos)) {
1921 			// Is this "our" flag?
1922 			if (flag->get_building() == location) {
1923 				if (state.ivar1 && flag->has_capacity()) {
1924 					if (WareInstance* const ware = fetch_carried_ware(game)) {
1925 						flag->add_ware(game, *ware);
1926 						set_animation(game, descr().get_animation("idle", this));
1927 						return schedule_act(game, 20);  //  rest a while
1928 					}
1929 				}
1930 
1931 				// Don't try to enter building if it is a dismantle site
1932 				// It is no problem for builders since they won't return before
1933 				// dismantling is complete.
1934 				if (is_a(DismantleSite, location)) {
1935 					set_location(nullptr);
1936 					return pop_task(game);
1937 				} else {
1938 					return start_task_move(
1939 					   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
1940 				}
1941 			}
1942 		}
1943 	}
1944 
1945 	// Determine the building's flag and move to it
1946 
1947 	if (!start_task_movepath(game, location->base_flag().get_position(), 15,
1948 	                         descr().get_right_walk_anims(does_carry_ware(), this))) {
1949 		molog("[return]: Failed to return\n");
1950 		const std::string message =
1951 		   (boost::format(_("Your %s can't find a way home and will likely die.")) %
1952 		    descr().descname().c_str())
1953 		      .str();
1954 
1955 		get_owner()->add_message(
1956 		   game, std::unique_ptr<Message>(new Message(
1957 		            Message::Type::kGameLogic, game.get_gametime(), _("Worker"),
1958 		            "images/ui_basic/menu_help.png", _("Worker got lost!"), message, get_position())),
1959 		   serial_);
1960 		set_location(nullptr);
1961 		return pop_task(game);
1962 	}
1963 }
1964 
1965 /**
1966  * Follow the steps of a configuration-defined program.
1967  * ivar1 is the next action to be performed.
1968  * ivar2 is used to store description indices selected by plant
1969  * objvar1 is used to store objects found by findobject
1970  * coords is used to store target coordinates found by findspace
1971  */
1972 const Bob::Task Worker::taskProgram = {"program", static_cast<Bob::Ptr>(&Worker::program_update),
1973                                        nullptr, static_cast<Bob::Ptr>(&Worker::program_pop), false};
1974 
1975 /**
1976  * Start the given program.
1977  */
start_task_program(Game & game,const std::string & programname)1978 void Worker::start_task_program(Game& game, const std::string& programname) {
1979 	push_task(game, taskProgram);
1980 	State& state = top_state();
1981 	state.program = descr().get_program(programname);
1982 	state.ivar1 = 0;
1983 }
1984 
program_update(Game & game,State & state)1985 void Worker::program_update(Game& game, State& state) {
1986 	if (get_signal().size()) {
1987 		molog("[program]: Interrupted by signal '%s'\n", get_signal().c_str());
1988 		return pop_task(game);
1989 	}
1990 
1991 	if (!state.program) {
1992 		// This might happen as fallout of some save game compatibility fix
1993 		molog("[program]: No program active\n");
1994 		send_signal(game, "fail");
1995 		return pop_task(game);
1996 	}
1997 
1998 	for (;;) {
1999 		const WorkerProgram& program = dynamic_cast<const WorkerProgram&>(*state.program);
2000 
2001 		if ((state.ivar1 >= 0) && (static_cast<uint32_t>(state.ivar1) >= program.get_size()))
2002 			return pop_task(game);
2003 
2004 		const Action& action = *program.get_action(state.ivar1);
2005 
2006 		if ((this->*(action.function))(game, state, action))
2007 			return;
2008 	}
2009 }
2010 
program_pop(Game & game,State & state)2011 void Worker::program_pop(Game& game, State& state) {
2012 	set_program_objvar(game, state, nullptr);
2013 }
2014 
set_program_objvar(Game & game,State & state,MapObject * obj)2015 void Worker::set_program_objvar(Game& game, State& state, MapObject* obj) {
2016 	assert(state.task == &taskProgram);
2017 
2018 	if (state.objvar1.get(game) != nullptr) {
2019 		(state.objvar1.get(game))->set_reserved_by_worker(false);
2020 	}
2021 
2022 	state.objvar1 = obj;
2023 
2024 	if (obj != nullptr) {
2025 		obj->set_reserved_by_worker(true);
2026 	}
2027 }
2028 
2029 const Bob::Task Worker::taskGowarehouse = {
2030    "gowarehouse", static_cast<Bob::Ptr>(&Worker::gowarehouse_update),
2031    static_cast<Bob::PtrSignal>(&Worker::gowarehouse_signalimmediate),
2032    static_cast<Bob::Ptr>(&Worker::gowarehouse_pop), true};
2033 
2034 /**
2035  * Get the worker to move to the nearest warehouse.
2036  * The worker is added to the list of usable wares, so he may be reassigned to
2037  * a new task immediately.
2038  */
start_task_gowarehouse(Game & game)2039 void Worker::start_task_gowarehouse(Game& game) {
2040 	assert(!supply_);
2041 
2042 	push_task(game, taskGowarehouse);
2043 }
2044 
gowarehouse_update(Game & game,State &)2045 void Worker::gowarehouse_update(Game& game, State& /* state */) {
2046 	PlayerImmovable* const location = get_location(game);
2047 
2048 	if (!location) {
2049 		send_signal(game, "location");
2050 		return pop_task(game);
2051 	}
2052 
2053 	// Signal handling
2054 	std::string signal = get_signal();
2055 
2056 	if (signal.size()) {
2057 		// if routing has failed, try a different warehouse/route on next update()
2058 		if (signal == "fail" || signal == "cancel") {
2059 			molog("[gowarehouse]: caught '%s'\n", signal.c_str());
2060 			signal_handled();
2061 		} else if (signal == "transfer") {
2062 			signal_handled();
2063 		} else {
2064 			molog("[gowarehouse]: cancel for signal '%s'\n", signal.c_str());
2065 			return pop_task(game);
2066 		}
2067 	}
2068 
2069 	if (dynamic_cast<Warehouse const*>(location)) {
2070 		delete supply_;
2071 		supply_ = nullptr;
2072 
2073 		schedule_incorporate(game);
2074 		return;
2075 	}
2076 
2077 	// If we got a transfer, use it
2078 	if (transfer_) {
2079 		Transfer* const t = transfer_;
2080 		transfer_ = nullptr;
2081 
2082 		molog("[gowarehouse]: Got transfer\n");
2083 
2084 		pop_task(game);
2085 		return start_task_transfer(game, t);
2086 	}
2087 
2088 	// Always leave buildings in an orderly manner,
2089 	// even when no warehouses are left to return to
2090 	if (location->descr().type() >= MapObjectType::BUILDING)
2091 		return start_task_leavebuilding(game, true);
2092 
2093 	if (!get_economy(wwWORKER)->warehouses().size()) {
2094 		molog("[gowarehouse]: No warehouse left in WorkerEconomy\n");
2095 		return pop_task(game);
2096 	}
2097 
2098 	// Idle until we are assigned a transfer.
2099 	// The delay length is rather arbitrary, but we need some kind of
2100 	// check against disappearing warehouses, or the worker will just
2101 	// idle on a flag until the end of days (actually, until either the
2102 	// flag is removed or a warehouse connects to the Economy).
2103 	if (!supply_)
2104 		supply_ = new IdleWorkerSupply(*this);
2105 
2106 	return start_task_idle(game, descr().get_animation("idle", this), 1000);
2107 }
2108 
gowarehouse_signalimmediate(Game &,State &,const std::string & signal)2109 void Worker::gowarehouse_signalimmediate(Game&, State& /* state */, const std::string& signal) {
2110 	if (signal == "transfer") {
2111 		// We are assigned a transfer, make sure our supply disappears immediately
2112 		// Otherwise, we might receive two transfers in a row.
2113 		delete supply_;
2114 		supply_ = nullptr;
2115 	}
2116 }
2117 
gowarehouse_pop(Game &,State &)2118 void Worker::gowarehouse_pop(Game&, State&) {
2119 	delete supply_;
2120 	supply_ = nullptr;
2121 
2122 	if (transfer_) {
2123 		transfer_->has_failed();
2124 		transfer_ = nullptr;
2125 	}
2126 }
2127 
2128 const Bob::Task Worker::taskDropoff = {
2129    "dropoff", static_cast<Bob::Ptr>(&Worker::dropoff_update), nullptr, nullptr, true};
2130 
2131 const Bob::Task Worker::taskReleaserecruit = {
2132    "releaserecruit", static_cast<Bob::Ptr>(&Worker::releaserecruit_update), nullptr, nullptr, true};
2133 
2134 /**
2135  * Walk to the building's flag, drop the given ware, and walk back inside.
2136  */
start_task_dropoff(Game & game,WareInstance & ware)2137 void Worker::start_task_dropoff(Game& game, WareInstance& ware) {
2138 	set_carried_ware(game, &ware);
2139 	push_task(game, taskDropoff);
2140 }
2141 
dropoff_update(Game & game,State &)2142 void Worker::dropoff_update(Game& game, State&) {
2143 	std::string signal = get_signal();
2144 
2145 	if (signal.size()) {
2146 		molog("[dropoff]: Interrupted by signal '%s'\n", signal.c_str());
2147 		return pop_task(game);
2148 	}
2149 
2150 	WareInstance* ware = get_carried_ware(game);
2151 	BaseImmovable* const location = game.map()[get_position()].get_immovable();
2152 
2153 	// If the building just got destroyed, pop the task
2154 	PlayerImmovable* current_location = get_location(game);
2155 	if (current_location == nullptr) {
2156 		molog("%s: Unable to dropoff ware in building at (%d,%d) - there is no building there\n",
2157 		      descr().name().c_str(), get_position().x, get_position().y);
2158 		return pop_task(game);
2159 	}
2160 
2161 #ifndef NDEBUG
2162 	Building* ploc = dynamic_cast<Building*>(current_location);
2163 	assert(ploc == location || &ploc->base_flag() == location);
2164 #endif
2165 
2166 	// Deliver the ware
2167 	if (ware) {
2168 		// We're in the building, walk onto the flag
2169 		if (upcast(Building, building, location)) {
2170 			if (start_task_waitforcapacity(game, building->base_flag())) {
2171 				return;
2172 			}
2173 
2174 			return start_task_leavebuilding(game, false);  //  exit throttle
2175 		}
2176 
2177 		// We're on the flag, drop the ware and pause a little
2178 		if (upcast(Flag, flag, location)) {
2179 			if (flag->has_capacity()) {
2180 				flag->add_ware(game, *fetch_carried_ware(game));
2181 
2182 				set_animation(game, descr().get_animation("idle", this));
2183 				return schedule_act(game, 50);
2184 			}
2185 
2186 			molog("[dropoff]: flag is overloaded\n");
2187 			start_task_move(
2188 			   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
2189 			return;
2190 		}
2191 
2192 		throw wexception("MO(%u): [dropoff]: not on building or on flag - fishy", serial());
2193 	}
2194 
2195 	// We don't have the ware any more, return home
2196 	if (location->descr().type() == MapObjectType::FLAG)
2197 		return start_task_move(
2198 		   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
2199 
2200 	if (location->descr().type() < MapObjectType::BUILDING)
2201 		throw wexception("MO(%u): [dropoff]: not on building on return", serial());
2202 
2203 	if (dynamic_cast<Warehouse const*>(location)) {
2204 		schedule_incorporate(game);
2205 		return;
2206 	}
2207 
2208 	// Our parent task should know what to do
2209 	return pop_task(game);
2210 }
2211 
2212 /// Give the recruit his diploma and say farwell to him.
start_task_releaserecruit(Game & game,Worker & recruit)2213 void Worker::start_task_releaserecruit(Game& game, Worker& recruit) {
2214 	push_task(game, taskReleaserecruit);
2215 	molog("Starting to release %s %u...\n", recruit.descr().name().c_str(), recruit.serial());
2216 	return schedule_act(game, 5000);
2217 }
2218 
releaserecruit_update(Game & game,State &)2219 void Worker::releaserecruit_update(Game& game, State&) {
2220 	molog("\t...done releasing recruit\n");
2221 	return pop_task(game);
2222 }
2223 
2224 /**
2225  * ivar1 is set to 0 if we should move to the flag and fetch the ware, and it
2226  * is set to 1 if we should move into the building.
2227  */
2228 const Bob::Task Worker::taskFetchfromflag = {
2229    "fetchfromflag", static_cast<Bob::Ptr>(&Worker::fetchfromflag_update), nullptr, nullptr, true};
2230 
2231 /**
2232  * Walk to the building's flag, fetch an ware from the flag that is destined for
2233  * the building, and walk back inside.
2234  */
start_task_fetchfromflag(Game & game)2235 void Worker::start_task_fetchfromflag(Game& game) {
2236 	push_task(game, taskFetchfromflag);
2237 	top_state().ivar1 = 0;
2238 }
2239 
fetchfromflag_update(Game & game,State & state)2240 void Worker::fetchfromflag_update(Game& game, State& state) {
2241 	std::string signal = get_signal();
2242 	if (signal.size()) {
2243 		if (signal == "location") {
2244 			molog("[fetchfromflag]: Building disappeared, become fugitive\n");
2245 			return pop_task(game);
2246 		}
2247 	}
2248 
2249 	PlayerImmovable& employer = *get_location(game);
2250 	PlayerImmovable* const location =
2251 	   dynamic_cast<PlayerImmovable*>(game.map().get_immovable(get_position()));
2252 
2253 	// If we haven't got the ware yet, walk onto the flag
2254 	if (!get_carried_ware(game) && !state.ivar1) {
2255 		if (dynamic_cast<Building const*>(location))
2256 			return start_task_leavebuilding(game, false);
2257 
2258 		state.ivar1 = 1;  //  force return to building
2259 
2260 		if (!location) {
2261 			// this can happen if the flag (and the building) is destroyed while
2262 			// the worker leaves the building.
2263 			molog("[fetchfromflag]: flag dissappeared - become fugitive");
2264 			return pop_task(game);
2265 		}
2266 
2267 		// The ware has decided that it doesn't want to go to us after all
2268 		// In order to return to the warehouse, we're switching to State_DropOff
2269 		if (WareInstance* const ware =
2270 		       dynamic_cast<Flag&>(*location).fetch_pending_ware(game, employer)) {
2271 			set_carried_ware(game, ware);
2272 		}
2273 
2274 		set_animation(game, descr().get_animation("idle", this));
2275 		return schedule_act(game, 20);
2276 	}
2277 
2278 	// Go back into the building
2279 	if (dynamic_cast<Flag const*>(location)) {
2280 		molog("[fetchfromflag]: return to building\n");
2281 
2282 		return start_task_move(
2283 		   game, WALK_NW, descr().get_right_walk_anims(does_carry_ware(), this), true);
2284 	}
2285 
2286 	if (!dynamic_cast<Building const*>(location)) {
2287 		// This can happen "naturally" if the building gets destroyed, but the
2288 		// flag is still there and the worker tries to enter from that flag.
2289 		// E.g. the player destroyed the building, it is destroyed, through an
2290 		// enemy player, or it got destroyed through rising water (atlantean
2291 		// scenario)
2292 		molog("[fetchfromflag]: building dissappeared - searching for alternative\n");
2293 		return pop_task(game);
2294 	}
2295 
2296 	assert(location == &employer);
2297 
2298 	molog("[fetchfromflag]: back home\n");
2299 
2300 	if (WareInstance* const ware = fetch_carried_ware(game)) {
2301 		if (ware->get_next_move_step(game) == location) {
2302 			ware->enter_building(game, *dynamic_cast<Building*>(location));
2303 		} else {
2304 			// The ware changed its mind and doesn't want to go to this building
2305 			// after all, so carry it back out.
2306 			// This can happen in the following subtle and rare race condition:
2307 			// We start the fetchfromflag task as the worker in an enhanceable building.
2308 			// While we walk back into the building with the ware, the player enhances
2309 			// the building, so that we now belong to the newly created construction site.
2310 			// Obviously the construction site no longer has any use for the ware.
2311 			molog("[fetchfromflag]: ware no longer wants to go into building, drop off\n");
2312 			pop_task(game);
2313 			start_task_dropoff(game, *ware);
2314 			return;
2315 		}
2316 	}
2317 
2318 	// We're back!
2319 	if (dynamic_cast<Warehouse const*>(location)) {
2320 		schedule_incorporate(game);
2321 		return;
2322 	}
2323 
2324 	return pop_task(game);  //  assume that our parent task knows what to do
2325 }
2326 
2327 /**
2328  * Wait for available capacity on a flag.
2329  */
2330 const Bob::Task Worker::taskWaitforcapacity = {
2331    "waitforcapacity", static_cast<Bob::Ptr>(&Worker::waitforcapacity_update), nullptr,
2332    static_cast<Bob::Ptr>(&Worker::waitforcapacity_pop), true};
2333 
2334 /**
2335  * Checks the capacity of the flag.
2336  *
2337  * If there is none, a wait task is pushed, and the worker is added to the
2338  * flag's wait queue. The function returns true in this case.
2339  * If the flag still has capacity, the function returns false and doesn't
2340  * act at all.
2341  */
start_task_waitforcapacity(Game & game,Flag & flag)2342 bool Worker::start_task_waitforcapacity(Game& game, Flag& flag) {
2343 	if (flag.has_capacity()) {
2344 		return false;
2345 	}
2346 
2347 	push_task(game, taskWaitforcapacity);
2348 
2349 	top_state().objvar1 = &flag;
2350 
2351 	flag.wait_for_capacity(game, *this);
2352 
2353 	return true;
2354 }
2355 
waitforcapacity_update(Game & game,State &)2356 void Worker::waitforcapacity_update(Game& game, State&) {
2357 	std::string signal = get_signal();
2358 
2359 	if (signal.size()) {
2360 		if (signal == "wakeup")
2361 			signal_handled();
2362 		return pop_task(game);
2363 	}
2364 
2365 	return skip_act();  //  wait indefinitely
2366 }
2367 
waitforcapacity_pop(Game & game,State & state)2368 void Worker::waitforcapacity_pop(Game& game, State& state) {
2369 	if (upcast(Flag, flag, state.objvar1.get(game)))
2370 		flag->skip_wait_for_capacity(game, *this);
2371 }
2372 
2373 /**
2374  * Called when the flag we waited on has now got capacity left.
2375  * Return true if we actually woke up due to this.
2376  */
wakeup_flag_capacity(Game & game,Flag & flag)2377 bool Worker::wakeup_flag_capacity(Game& game, Flag& flag) {
2378 	if (State const* const state = get_state())
2379 		if (state->task == &taskWaitforcapacity) {
2380 			molog("[waitforcapacity]: Wake up: flag capacity.\n");
2381 
2382 			if (state->objvar1.get(game) != &flag)
2383 				throw wexception("MO(%u): wakeup_flag_capacity: Flags do not match.", serial());
2384 
2385 			send_signal(game, "wakeup");
2386 			return true;
2387 		}
2388 
2389 	return false;
2390 }
2391 
2392 /**
2393  * ivar1 - 0: don't change location; 1: change location to the flag
2394  * objvar1 - the building we're leaving
2395  */
2396 const Bob::Task Worker::taskLeavebuilding = {
2397    "leavebuilding", static_cast<Bob::Ptr>(&Worker::leavebuilding_update), nullptr,
2398    static_cast<Bob::Ptr>(&Worker::leavebuilding_pop), true};
2399 
2400 /**
2401  * Leave the current building.
2402  * Waits on the buildings leave wait queue if necessary.
2403  *
2404  * If changelocation is true, change the location to the flag once we're
2405  * outside.
2406  */
start_task_leavebuilding(Game & game,bool const changelocation)2407 void Worker::start_task_leavebuilding(Game& game, bool const changelocation) {
2408 	// Set the wait task
2409 	push_task(game, taskLeavebuilding);
2410 	State& state = top_state();
2411 	state.ivar1 = changelocation;
2412 	state.objvar1 = &dynamic_cast<Building&>(*get_location(game));
2413 }
2414 
leavebuilding_update(Game & game,State & state)2415 void Worker::leavebuilding_update(Game& game, State& state) {
2416 	const std::string& signal = get_signal();
2417 
2418 	if (signal == "wakeup")
2419 		signal_handled();
2420 	else if (signal.size())
2421 		return pop_task(game);
2422 
2423 	upcast(Building, building, get_location(game));
2424 	if (!building) {
2425 		return pop_task(game);
2426 	}
2427 
2428 	Flag& baseflag = building->base_flag();
2429 
2430 	if (get_position() == building->get_position()) {
2431 		assert(building == state.objvar1.get(game));
2432 		if (!building->leave_check_and_wait(game, *this))
2433 			return skip_act();
2434 
2435 		if (state.ivar1)
2436 			set_location(&baseflag);
2437 
2438 		return start_task_move(
2439 		   game, WALK_SE, descr().get_right_walk_anims(does_carry_ware(), this), true);
2440 	} else {
2441 		const Coords& flagpos = baseflag.get_position();
2442 
2443 		if (state.ivar1)
2444 			set_location(&baseflag);
2445 
2446 		if (get_position() == flagpos)
2447 			return pop_task(game);
2448 
2449 		if (!start_task_movepath(
2450 		       game, flagpos, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
2451 			molog("[leavebuilding]: outside of building, but failed to walk back to flag");
2452 			set_location(nullptr);
2453 			return pop_task(game);
2454 		}
2455 		return;
2456 	}
2457 }
2458 
leavebuilding_pop(Game & game,State & state)2459 void Worker::leavebuilding_pop(Game& game, State& state) {
2460 	// As of this writing, this is only really necessary when the task
2461 	// is interrupted by a signal. Putting this in the pop() method is just
2462 	// defensive programming, in case leavebuilding_update() changes
2463 	// in the future.
2464 	//
2465 	//  The if-statement is needed because this is (unfortunately) also called
2466 	//  when the Worker is deallocated when shutting down the simulation. Then
2467 	//  the building might not exist any more.
2468 	if (MapObject* const building = state.objvar1.get(game)) {
2469 		dynamic_cast<Building&>(*building).leave_skip(game, *this);
2470 	}
2471 }
2472 
2473 /**
2474  * Called when the given building allows us to leave it.
2475  * \return true if we actually woke up due to this.
2476  */
wakeup_leave_building(Game & game,Building & building)2477 bool Worker::wakeup_leave_building(Game& game, Building& building) {
2478 	if (State const* const state = get_state())
2479 		if (state->task == &taskLeavebuilding) {
2480 			if (state->objvar1.get(game) != &building)
2481 				throw wexception("MO(%u): [waitleavebuilding]: buildings do not match", serial());
2482 
2483 			send_signal(game, "wakeup");
2484 			return true;
2485 		}
2486 
2487 	return false;
2488 }
2489 
2490 /**
2491  * Run around aimlessly until we find a warehouse.
2492  */
2493 const Bob::Task Worker::taskFugitive = {
2494    "fugitive", static_cast<Bob::Ptr>(&Worker::fugitive_update), nullptr, nullptr, true};
2495 
start_task_fugitive(Game & game)2496 void Worker::start_task_fugitive(Game& game) {
2497 	push_task(game, taskFugitive);
2498 
2499 	// Fugitives survive for two to four minutes
2500 	top_state().ivar1 = game.get_gametime() + 120000 + 200 * (game.logic_rand() % 600);
2501 }
2502 
2503 struct FindFlagWithPlayersWarehouse {
FindFlagWithPlayersWarehouseWidelands::FindFlagWithPlayersWarehouse2504 	explicit FindFlagWithPlayersWarehouse(const Player& owner) : owner_(owner) {
2505 	}
acceptWidelands::FindFlagWithPlayersWarehouse2506 	bool accept(const BaseImmovable& imm) const {
2507 		if (upcast(Flag const, flag, &imm)) {
2508 			if (flag->get_owner() == &owner_) {
2509 				if (!flag->economy(wwWORKER).warehouses().empty()) {
2510 					return true;
2511 				}
2512 			}
2513 		}
2514 		return false;
2515 	}
2516 
2517 private:
2518 	const Player& owner_;
2519 };
2520 
fugitive_update(Game & game,State & state)2521 void Worker::fugitive_update(Game& game, State& state) {
2522 	if (get_signal().size()) {
2523 		molog("[fugitive]: interrupted by signal '%s'\n", get_signal().c_str());
2524 		return pop_task(game);
2525 	}
2526 
2527 	const Map& map = game.map();
2528 	PlayerImmovable const* location = get_location(game);
2529 
2530 	if (location && location->get_owner() == get_owner()) {
2531 		molog("[fugitive]: we are on location\n");
2532 
2533 		if (dynamic_cast<Warehouse const*>(location))
2534 			return schedule_incorporate(game);
2535 
2536 		set_location(nullptr);
2537 		location = nullptr;
2538 	}
2539 
2540 	// check whether we're on a flag and it's time to return home
2541 	if (upcast(Flag, flag, map[get_position()].get_immovable())) {
2542 		if (flag->get_owner() == get_owner() && flag->economy(wwWORKER).warehouses().size()) {
2543 			set_location(flag);
2544 			return pop_task(game);
2545 		}
2546 	}
2547 
2548 	// Try to find a flag connected to a warehouse that we can return to
2549 	//
2550 	// We always have a high probability to see flags within our vision range,
2551 	// but with some luck we see flags that are even further away.
2552 	std::vector<ImmovableFound> flags;
2553 	uint32_t vision = descr().vision_range();
2554 	uint32_t maxdist = 4 * vision;
2555 	if (map.find_immovables(game, Area<FCoords>(map.get_fcoords(get_position()), maxdist), &flags,
2556 	                        FindFlagWithPlayersWarehouse(*get_owner()))) {
2557 		uint32_t bestdist = 0;
2558 		Flag* best = nullptr;
2559 
2560 		molog("[fugitive]: found a flag connected to warehouse(s)\n");
2561 		for (const ImmovableFound& tmp_flag : flags) {
2562 
2563 			Flag& flag = dynamic_cast<Flag&>(*tmp_flag.object);
2564 
2565 			if (game.logic_rand() % 2 == 0)
2566 				continue;
2567 
2568 			uint32_t const dist = map.calc_distance(get_position(), tmp_flag.coords);
2569 
2570 			if (!best || bestdist > dist) {
2571 				best = &flag;
2572 				bestdist = dist;
2573 			}
2574 		}
2575 
2576 		if (best && bestdist > vision) {
2577 			uint32_t chance = maxdist - (bestdist - vision);
2578 			if (game.logic_rand() % maxdist >= chance)
2579 				best = nullptr;
2580 		}
2581 
2582 		if (best) {
2583 			molog("[fugitive]: try to move to flag\n");
2584 
2585 			// Warehouse could be on a different island, so check for failure
2586 			// Also, move only a few number of steps in the right direction,
2587 			// so that we could theoretically lose the flag again, but also
2588 			// perhaps find a closer flag.
2589 			if (start_task_movepath(game, best->get_position(), 0,
2590 			                        descr().get_right_walk_anims(does_carry_ware(), this), false, 4))
2591 				return;
2592 		}
2593 	}
2594 
2595 	if ((state.ivar1 < 0) ||
2596 	    (static_cast<uint32_t>(state.ivar1) < game.get_gametime())) {  //  time to die?
2597 		molog("[fugitive]: die\n");
2598 		return schedule_destroy(game);
2599 	}
2600 
2601 	molog("[fugitive]: wander randomly\n");
2602 
2603 	if (start_task_movepath(game, game.random_location(get_position(), descr().vision_range()), 4,
2604 	                        descr().get_right_walk_anims(does_carry_ware(), this)))
2605 		return;
2606 
2607 	return start_task_idle(game, descr().get_animation("idle", this), 50);
2608 }
2609 
2610 /**
2611  * Walk in a circle around our owner, calling a subprogram on currently
2612  * empty fields.
2613  *
2614  * ivar1 - number of attempts
2615  * ivar2 - radius to search
2616  * svar1 - name of subcommand
2617  *
2618  * Failure of path movement is caught, all other signals terminate this task.
2619  */
2620 const Bob::Task Worker::taskGeologist = {
2621    "geologist", static_cast<Bob::Ptr>(&Worker::geologist_update), nullptr, nullptr, true};
2622 
start_task_geologist(Game & game,uint8_t const attempts,uint8_t const radius,const std::string & subcommand)2623 void Worker::start_task_geologist(Game& game,
2624                                   uint8_t const attempts,
2625                                   uint8_t const radius,
2626                                   const std::string& subcommand) {
2627 	push_task(game, taskGeologist);
2628 	State& state = top_state();
2629 	state.ivar1 = attempts;
2630 	state.ivar2 = radius;
2631 	state.svar1 = subcommand;
2632 }
2633 
geologist_update(Game & game,State & state)2634 void Worker::geologist_update(Game& game, State& state) {
2635 	std::string signal = get_signal();
2636 
2637 	if (signal == "fail") {
2638 		molog("[geologist]: Caught signal '%s'\n", signal.c_str());
2639 		signal_handled();
2640 	} else if (signal.size()) {
2641 		molog("[geologist]: Interrupted by signal '%s'\n", signal.c_str());
2642 		return pop_task(game);
2643 	}
2644 
2645 	//
2646 	const Map& map = game.map();
2647 	const World& world = game.world();
2648 	Area<FCoords> owner_area(
2649 	   map.get_fcoords(dynamic_cast<Flag&>(*get_location(game)).get_position()), state.ivar2);
2650 
2651 	// Check if it's not time to go home
2652 	if (state.ivar1 > 0) {
2653 		// Check to see if we're on suitable terrain
2654 		BaseImmovable* const imm = map.get_immovable(get_position());
2655 
2656 		if (!imm || (imm->get_size() == BaseImmovable::NONE && !imm->has_attribute(RESI))) {
2657 			--state.ivar1;
2658 			return start_task_program(game, state.svar1);
2659 		}
2660 
2661 		// Find a suitable field and walk towards it
2662 		std::vector<Coords> list;
2663 		CheckStepDefault cstep(descr().movecaps());
2664 		FindNodeAnd ffa;
2665 
2666 		ffa.add(FindNodeImmovableSize(FindNodeImmovableSize::sizeNone), false);
2667 		ffa.add(FindNodeImmovableAttribute(RESI), true);
2668 
2669 		if (map.find_reachable_fields(game, owner_area, &list, cstep, ffa)) {
2670 			FCoords target;
2671 
2672 			// is center a mountain piece?
2673 			bool is_center_mountain = (world.terrain_descr(owner_area.field->terrain_d()).get_is() &
2674 			                           TerrainDescription::Is::kMineable) |
2675 			                          (world.terrain_descr(owner_area.field->terrain_r()).get_is() &
2676 			                           TerrainDescription::Is::kMineable);
2677 			// Only run towards fields that are on a mountain (or not)
2678 			// depending on position of center
2679 			bool is_target_mountain;
2680 			uint32_t n = list.size();
2681 			assert(n);
2682 			uint32_t i = game.logic_rand() % n;
2683 			do {
2684 				target = map.get_fcoords(list[game.logic_rand() % list.size()]);
2685 				is_target_mountain = (world.terrain_descr(target.field->terrain_d()).get_is() &
2686 				                      TerrainDescription::Is::kMineable) |
2687 				                     (world.terrain_descr(target.field->terrain_r()).get_is() &
2688 				                      TerrainDescription::Is::kMineable);
2689 				if (i == 0)
2690 					i = list.size();
2691 				--i;
2692 				--n;
2693 			} while ((is_center_mountain != is_target_mountain) && n);
2694 
2695 			if (!n) {
2696 				// no suitable field found, this is no fail, there's just
2697 				// nothing else to do so let's go home
2698 				// FALLTHROUGH TO RETURN HOME
2699 			} else {
2700 				if (!start_task_movepath(
2701 				       game, target, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
2702 
2703 					molog("[geologist]: Bug: could not find path\n");
2704 					send_signal(game, "fail");
2705 					return pop_task(game);
2706 				}
2707 				return;
2708 			}
2709 		}
2710 
2711 		state.ivar1 = 0;
2712 	}
2713 
2714 	if (get_position() == owner_area)
2715 		return pop_task(game);
2716 
2717 	if (!start_task_movepath(
2718 	       game, owner_area, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
2719 		molog("[geologist]: could not find path home\n");
2720 		send_signal(game, "fail");
2721 		return pop_task(game);
2722 	}
2723 }
2724 
2725 /**
2726  * Look at fields that are in the fog of war around our owner.
2727  *
2728  * ivar1 - radius to start searching
2729  * ivar2 - time to spend
2730  *
2731  * Failure of path movement is caught, all other signals terminate this task.
2732  */
2733 const Bob::Task Worker::taskScout = {
2734    "scout", static_cast<Bob::Ptr>(&Worker::scout_update), nullptr, nullptr, true};
2735 
2736 /**
2737  * scout=\<radius\> \<time\>
2738  *
2739  * Find a spot that is in the fog of war and go there to see what's up.
2740  *
2741  * iparam1 = radius where the scout initially searches for unseen fields
2742  * iparam2 = maximum search time (in msecs)
2743  */
run_scout(Game & game,State & state,const Action & action)2744 bool Worker::run_scout(Game& game, State& state, const Action& action) {
2745 	molog("  Try scouting for %i ms with search in radius of %i\n", action.iparam2, action.iparam1);
2746 	if (upcast(ProductionSite, productionsite, get_location(game))) {
2747 		productionsite->unnotify_player();
2748 	}
2749 	++state.ivar1;
2750 	start_task_scout(game, action.iparam1, action.iparam2);
2751 	// state reference may be invalid now
2752 	return true;
2753 }
2754 
2755 /** Setup scouts_worklist at the start of its task.
2756  *
2757  * The first element of scouts_worklist vector stores the location of my hut, at the time of
2758  * creation.
2759  * If the building location changes, then pop the now-obsolete list of points of interest
2760  */
prepare_scouts_worklist(const Map & map,const Coords & hutpos)2761 void Worker::prepare_scouts_worklist(const Map& map, const Coords& hutpos) {
2762 
2763 	if (!scouts_worklist.empty()) {
2764 		if (map.calc_distance(scouts_worklist[0].scoutme, hutpos) != 0) {
2765 			// Hut has been relocated, I must rebuild the worklist
2766 			scouts_worklist.clear();
2767 		}
2768 	}
2769 
2770 	if (scouts_worklist.empty()) {
2771 		// Store the position of homebase
2772 		const PlaceToScout home(hutpos);
2773 		scouts_worklist.push_back(home);
2774 	} else if (1 < scouts_worklist.size()) {
2775 		// If there was an old place to visit in queue, remove it.
2776 		scouts_worklist.pop_back();
2777 	}
2778 }
2779 
2780 /** Check if militray sites have become visible by now.
2781  *
2782  * After the pop in prepare_scouts_worklist,
2783  * the latest entry of scouts_worklist is the next MS to visit (if known)
2784  * Check whether it is still interesting (=whether it is still invisible)
2785  */
check_visible_sites(const Map & map,const Player & player)2786 void Worker::check_visible_sites(const Map& map, const Player& player) {
2787 	while (1 < scouts_worklist.size()) {
2788 		if (scouts_worklist.back().randomwalk) {
2789 			return;  // Random walk never goes out of fashion.
2790 		} else {
2791 			MapIndex mt = map.get_index(scouts_worklist.back().scoutme, map.get_width());
2792 			if (1 < player.vision(mt)) {
2793 				// The military site is now visible. Either player
2794 				// has acquired possession of more military sites
2795 				// of own, or own folks are nearby.
2796 				scouts_worklist.pop_back();
2797 			} else {
2798 				return;
2799 			}
2800 		}
2801 	}
2802 }
2803 
2804 /** Make a plan which militar sites (if any) to visit.
2805  *
2806  * @param found_sites list of miliar sites to consider.
2807  */
add_sites(Game & game,const Map & map,const Player & player,std::vector<ImmovableFound> & found_sites)2808 void Worker::add_sites(Game& game,
2809                        const Map& map,
2810                        const Player& player,
2811                        std::vector<ImmovableFound>& found_sites) {
2812 
2813 	// If there are many enemy sites, push a random walk request into queue every third finding.
2814 	uint32_t haveabreak = 3;
2815 
2816 	for (const ImmovableFound& vu : found_sites) {
2817 		upcast(Flag, aflag, vu.object);
2818 		Building* a_building = aflag->get_building();
2819 		// Assuming that this always succeeds.
2820 		if (a_building->descr().type() == MapObjectType::MILITARYSITE) {
2821 			// This would be safe even if this assert failed: Own militarysites are always visible.
2822 			// However: There would be something wrong with FindForeignMilitarySite or associated
2823 			// code. Hence, let's keep the assert.
2824 			assert(a_building->get_owner() != &player);
2825 			const Coords buildingpos = a_building->get_positions(game)[0];
2826 			// Check the visibility: only invisible ones interest the scout.
2827 			MapIndex mx = map.get_index(buildingpos, map.get_width());
2828 			if (2 > player.vision(mx)) {
2829 				// The find_reachable_immovable sometimes returns multiple instances.
2830 				// TODO(kxq): Is that okay? This could be a performance issue elsewhere.
2831 				// Let's not add duplicates to my work list.
2832 				bool unique = true;
2833 				unsigned worklist_size = scouts_worklist.size();
2834 				for (unsigned t = 1; t < worklist_size; t++) {
2835 					if (buildingpos.x == scouts_worklist[t].scoutme.x &&
2836 					    buildingpos.y == scouts_worklist[t].scoutme.y) {
2837 						unique = false;
2838 						break;
2839 					}
2840 				}
2841 				if (unique) {
2842 					if (1 > --haveabreak) {
2843 						// If there are many MSs to visit, do a random walk in-between also.
2844 						haveabreak = 3;
2845 						const PlaceToScout randomwalk;
2846 						scouts_worklist.push_back(randomwalk);
2847 					}
2848 					// if vision is zero, blacked out.
2849 					// if vision is one, old info exists; unattackable.
2850 					// When entering here, the place is worth scouting.
2851 					const PlaceToScout go_there(buildingpos);
2852 					scouts_worklist.push_back(go_there);
2853 				}
2854 			}
2855 		}
2856 	}
2857 
2858 	// I suppose that this never triggers. Anyway. In savegame, I assume that the vector
2859 	// length fits to eight bits. If the entire search area of the scout is full of
2860 	// enemy military sites that are invisible to player, >254 would be possible.
2861 	// Therefore,
2862 	while (254 < scouts_worklist.size()) {
2863 		scouts_worklist.pop_back();
2864 	}
2865 	// (the limit is 254 not 255, since one randomwalk is unconditionally pushed in later)
2866 }
2867 
2868 /**
2869  * Make scout walk random or lurking around some military site.
2870  *
2871  * Enemy military sites cannot be attacked, if those are not visible.
2872  * However, Widelands workers are still somewhat aware of their presence. For example,
2873  * Player does not acquire ownership of land, even if a militarysite blocking it is
2874  * invisible. Therefore, it is IMO okay for the scout to be aware of those as well.
2875  * Scout occasionally pays special attention to enemy military sites, to give the player
2876  * an opportunity to attack. This is important, if the player can only build small huts
2877  * and the enemy has one of the biggest ones: without scout, the player has no way of attacking.
2878  */
start_task_scout(Game & game,uint16_t const radius,uint32_t const time)2879 void Worker::start_task_scout(Game& game, uint16_t const radius, uint32_t const time) {
2880 	push_task(game, taskScout);
2881 	State& state = top_state();
2882 	state.ivar1 = radius;
2883 	state.ivar2 = game.get_gametime() + time;
2884 
2885 	// The following code switches between two modes of operation:
2886 	// - Random walk
2887 	// - Lurking near an enemy military site.
2888 	// The code keeps track of interesting military sites, so that they all are visited.
2889 	// When the list of unvisited potential attack targets is exhausted, the list is rebuilt.
2890 	// The first element in the vector is special: It is used to store the location of the scout's
2891 	// hut at the moment of creation. If player dismantles the site and builds a new, the old
2892 	// points of interest are no longer valid and the list is cleared.
2893 	// Random remarks:
2894 	// Some unattackable military sites are also visited (like one under construction).
2895 	// Also, dismantled buildings may end up here. I do not consider these bugs, but if somebody
2896 	// reports, the behavior can always be altered.
2897 
2898 	const FCoords& bobpos = get_position();
2899 	assert(nullptr != bobpos.field);
2900 
2901 	// Some assumptions: When scout starts working, he is located in his hut.
2902 	// I cannot imagine any situations where this is not the case. However,
2903 	// such situation could trigger bugs.
2904 	const BaseImmovable* homebase = bobpos.field->get_immovable();
2905 	assert(nullptr != homebase);
2906 
2907 	const Coords hutpos = homebase->get_positions(game)[0];
2908 	const Map& map = game.map();
2909 	const Player& player = owner();
2910 
2911 	// The prepare-routine checks that the list or places to visit is still valid,
2912 	// plus pushes in the "first entry", which is used to x-check the validity.
2913 	prepare_scouts_worklist(map, hutpos);
2914 
2915 	// If an enemy military site has become visible, this removes it from the work list.
2916 	// Note that dismantled/burnt military sites are *not* removed from work list.
2917 	// These changes are still somewhat interesting.
2918 	// TODO(kxq): Ideally, if the military site has been dismantled/burnt, then the
2919 	// scout should not spend that long around, but revert to random walking after
2920 	// first visiting the dismantlesite/ruins.
2921 	check_visible_sites(map, player);
2922 
2923 	// Check whether there is still undone work in the queue,
2924 	// keeping in mind that 1st element of the vector is special
2925 	if (2 > scouts_worklist.size()) {
2926 		assert(!scouts_worklist.empty());
2927 		// If there was only one entry, worklist has been exhausted. Rebuild it.
2928 		// Time to find new places worth visiting.
2929 		Area<FCoords> revealations(map.get_fcoords(get_position()), state.ivar1);
2930 		std::vector<ImmovableFound> found_sites;
2931 		CheckStepWalkOn csteb(MOVECAPS_WALK, true);
2932 		map.find_reachable_immovables(
2933 		   game, revealations, &found_sites, csteb, FindFlagOf(FindForeignMilitarysite(player)));
2934 
2935 		add_sites(game, map, player, found_sites);
2936 
2937 		// Always push a "go-anywhere" -directive into work list.
2938 		const PlaceToScout gosomewhere;
2939 		scouts_worklist.push_back(gosomewhere);
2940 	}
2941 
2942 	// first get out
2943 	push_task(game, taskLeavebuilding);
2944 	State& stateLeave = top_state();
2945 	stateLeave.ivar1 = false;
2946 	stateLeave.objvar1 = &dynamic_cast<Building&>(*get_location(game));
2947 }
2948 
scout_random_walk(Game & game,const Map & map,State & state)2949 bool Worker::scout_random_walk(Game& game, const Map& map, State& state) {
2950 
2951 	Coords oldest_coords = get_position();
2952 
2953 	std::vector<Coords> list;  //< List of interesting points
2954 	CheckStepDefault cstep(descr().movecaps());
2955 	FindNodeAnd ffa;
2956 	ffa.add(FindNodeImmovableSize(FindNodeImmovableSize::sizeNone), false);
2957 	Area<FCoords> exploring_area(map.get_fcoords(get_position()), state.ivar1);
2958 	Time oldest_time = game.get_gametime();
2959 
2960 	// if some fields can be reached
2961 	if (map.find_reachable_fields(game, exploring_area, &list, cstep, ffa) > 0) {
2962 		// Parse randomly the reachable fields, maximum 50 iterations
2963 		uint8_t iterations = list.size() % 51;
2964 		uint8_t oldest_distance = 0;
2965 		for (uint8_t i = 0; i < iterations; ++i) {
2966 			const std::vector<Coords>::size_type lidx = game.logic_rand() % list.size();
2967 			Coords const coord = list[lidx];
2968 			list.erase(list.begin() + lidx);
2969 			MapIndex idx = map.get_index(coord, map.get_width());
2970 			Vision const visible = owner().vision(idx);
2971 
2972 			// If the field is not yet discovered, go there
2973 			if (!visible) {
2974 				molog("[scout]: Go to interesting field (%i, %i)\n", coord.x, coord.y);
2975 				if (!start_task_movepath(
2976 				       game, coord, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
2977 					molog("[scout]: failed to reach destination\n");
2978 					return false;
2979 				} else {
2980 					return true;  // start_task_movepath was successfull.
2981 				}
2982 			}
2983 
2984 			// Else evaluate for second best target
2985 			int dist = map.calc_distance(coord, get_position());
2986 			Time time = owner().fields()[idx].time_node_last_unseen;
2987 			// time is only valid if visible is 1
2988 			if (visible != 1)
2989 				time = oldest_time;
2990 
2991 			if (dist > oldest_distance || (dist == oldest_distance && time < oldest_time)) {
2992 				oldest_distance = dist;
2993 				oldest_time = time;
2994 				oldest_coords = coord;
2995 			}
2996 		}
2997 
2998 		// All fields discovered, go to second choice target
2999 
3000 		if (oldest_coords != get_position()) {
3001 			molog(
3002 			   "[scout]: All fields discovered. Go to (%i, %i)\n", oldest_coords.x, oldest_coords.y);
3003 			if (!start_task_movepath(
3004 			       game, oldest_coords, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
3005 				molog("[scout]: Failed to reach destination\n");
3006 				return false;  // If failed go home
3007 			} else
3008 				return true;  // Start task movepath success.
3009 		}
3010 	}
3011 	// No reachable fields found.
3012 	molog("[scout]: nowhere to go!\n");
3013 	return false;
3014 }
3015 
3016 /** Make scout hang around an enemy military site.
3017  *
3018  */
scout_lurk_around(Game & game,const Map & map,struct Worker::PlaceToScout & scoutat)3019 bool Worker::scout_lurk_around(Game& game, const Map& map, struct Worker::PlaceToScout& scoutat) {
3020 
3021 	Coords oldest_coords = get_position();
3022 
3023 	std::vector<Coords> surrounding_places;  // locations near the MS under inspection
3024 	CheckStepDefault cstep(descr().movecaps());
3025 	FindNodeAnd fna;
3026 	fna.add(FindNodeImmovableSize(FindNodeImmovableSize::sizeNone), false);
3027 
3028 	// scoutat points to the enemy military site; walk in random at vicinity.
3029 	// First try some near-close fields. If no success then try some further off ones.
3030 	// This code is partially copied from scout_random_walk(); I did not check why
3031 	// start_task_movepath
3032 	// would fail. Therefore, the looping can be a bit silly to more knowledgeable readers.
3033 	for (unsigned vicinity = 1; vicinity < 4; vicinity++) {
3034 		Area<FCoords> exploring_area(map.get_fcoords(scoutat.scoutme), vicinity);
3035 		if (map.find_reachable_fields(game, exploring_area, &surrounding_places, cstep, fna) > 0) {
3036 			unsigned formax = surrounding_places.size();
3037 			if (3 + vicinity < formax) {
3038 				formax = 3 + vicinity;
3039 			}
3040 			for (uint8_t i = 0; i < formax; ++i) {
3041 				const std::vector<Coords>::size_type l_idx =
3042 				   game.logic_rand() % surrounding_places.size();
3043 				Coords const coord = surrounding_places[l_idx];
3044 				surrounding_places.erase(surrounding_places.begin() + l_idx);
3045 				// The variable name "oldest_coords" makes sense in the "random walk" branch.
3046 				// Here, it simply is the current position of the scout.
3047 				if (coord.x != oldest_coords.x || coord.y != oldest_coords.y) {
3048 					if (!start_task_movepath(
3049 					       game, coord, 0, descr().get_right_walk_anims(does_carry_ware(), this))) {
3050 						molog("[scout]: failed to reach destination (x)\n");
3051 						return false;
3052 					} else {
3053 						return true;  // start_task_movepath was successfull.
3054 					}
3055 				}
3056 			}
3057 		}
3058 	}
3059 	return false;
3060 }
3061 
scout_update(Game & game,State & state)3062 void Worker::scout_update(Game& game, State& state) {
3063 	const std::string& signal = get_signal();
3064 	molog("  Update Scout (%i time)\n", state.ivar2);
3065 
3066 	if (signal.size()) {
3067 		molog("[scout]: Interrupted by signal '%s'\n", signal.c_str());
3068 		return pop_task(game);
3069 	}
3070 
3071 	const Map& map = game.map();
3072 
3073 	const bool do_run = static_cast<int32_t>(state.ivar2 - game.get_gametime()) > 0;
3074 
3075 	// do not pop; this function is called many times per run.
3076 	struct PlaceToScout scoutat = scouts_worklist.back();
3077 
3078 	// If not yet time to go home
3079 	if (do_run) {
3080 		if (scoutat.randomwalk) {
3081 			if (scout_random_walk(game, map, state))
3082 				return;
3083 		} else {
3084 			if (scout_lurk_around(game, map, scoutat))
3085 				return;
3086 		}
3087 	}
3088 	// time to go home or found nothing to go to
3089 	pop_task(game);
3090 	schedule_act(game, 10);
3091 }
3092 
draw_inner(const EditorGameBase & game,const Vector2f & point_on_dst,const Coords & coords,const float scale,RenderTarget * dst) const3093 void Worker::draw_inner(const EditorGameBase& game,
3094                         const Vector2f& point_on_dst,
3095                         const Coords& coords,
3096                         const float scale,
3097                         RenderTarget* dst) const {
3098 	assert(get_owner() != nullptr);
3099 	const RGBColor& player_color = get_owner()->get_playercolor();
3100 
3101 	dst->blit_animation(point_on_dst, coords, scale, get_current_anim(),
3102 	                    game.get_gametime() - get_animstart(), &player_color);
3103 
3104 	if (WareInstance const* const carried_ware = get_carried_ware(game)) {
3105 		const Vector2f hotspot = descr().ware_hotspot().cast<float>();
3106 		const Vector2f location(
3107 		   point_on_dst.x - hotspot.x * scale, point_on_dst.y - hotspot.y * scale);
3108 		dst->blit_animation(location, Widelands::Coords::null(), scale,
3109 		                    carried_ware->descr().get_animation("idle", this), 0, &player_color);
3110 	}
3111 }
3112 
3113 /**
3114  * Draw the worker, taking the carried ware into account.
3115  */
draw(const EditorGameBase & egbase,const InfoToDraw &,const Vector2f & field_on_dst,const Widelands::Coords & coords,const float scale,RenderTarget * dst) const3116 void Worker::draw(const EditorGameBase& egbase,
3117                   const InfoToDraw&,
3118                   const Vector2f& field_on_dst,
3119                   const Widelands::Coords& coords,
3120                   const float scale,
3121                   RenderTarget* dst) const {
3122 	if (!get_current_anim()) {
3123 		return;
3124 	}
3125 	draw_inner(egbase, calc_drawpos(egbase, field_on_dst, scale), coords, scale, dst);
3126 }
3127 
3128 /*
3129 ==============================
3130 
3131 Load/save support
3132 
3133 ==============================
3134 */
3135 
3136 constexpr uint8_t kCurrentPacketVersion = 3;
3137 
Loader()3138 Worker::Loader::Loader() : location_(0), carried_ware_(0) {
3139 }
3140 
load(FileRead & fr)3141 void Worker::Loader::load(FileRead& fr) {
3142 	Bob::Loader::load(fr);
3143 	try {
3144 		const uint8_t packet_version = fr.unsigned_8();
3145 		if (packet_version == kCurrentPacketVersion) {
3146 
3147 			Worker& worker = get<Worker>();
3148 			location_ = fr.unsigned_32();
3149 			carried_ware_ = fr.unsigned_32();
3150 			worker.current_exp_ = fr.signed_32();
3151 
3152 			if (fr.unsigned_8()) {
3153 				worker.transfer_ = new Transfer(dynamic_cast<Game&>(egbase()), worker);
3154 				worker.transfer_->read(fr, transfer_);
3155 			}
3156 			const unsigned veclen = fr.unsigned_8();
3157 			for (unsigned q = 0; q < veclen; q++) {
3158 				if (fr.unsigned_8()) {
3159 					const PlaceToScout gsw;
3160 					worker.scouts_worklist.push_back(gsw);
3161 				} else {
3162 					const int16_t x = fr.signed_16();
3163 					const int16_t y = fr.signed_16();
3164 					Coords peekpos = Coords(x, y);
3165 					const PlaceToScout gtt(peekpos);
3166 					worker.scouts_worklist.push_back(gtt);
3167 				}
3168 			}
3169 
3170 		} else {
3171 			throw UnhandledVersionError("Worker", packet_version, kCurrentPacketVersion);
3172 		}
3173 	} catch (const std::exception& e) {
3174 		throw wexception("loading worker: %s", e.what());
3175 	}
3176 }
3177 
load_pointers()3178 void Worker::Loader::load_pointers() {
3179 	Bob::Loader::load_pointers();
3180 
3181 	Worker& worker = get<Worker>();
3182 
3183 	if (location_)
3184 		worker.set_location(&mol().get<PlayerImmovable>(location_));
3185 	if (carried_ware_)
3186 		worker.carried_ware_ = &mol().get<WareInstance>(carried_ware_);
3187 	if (worker.transfer_)
3188 		worker.transfer_->read_pointers(mol(), transfer_);
3189 }
3190 
load_finish()3191 void Worker::Loader::load_finish() {
3192 	Bob::Loader::load_finish();
3193 
3194 	Worker& worker = get<Worker>();
3195 
3196 	// If our economy is unclear because we have no location, it is wise to not
3197 	// mess with it. For example ships will not be a location for Workers
3198 	// (because they are no PlayerImmovable), but they will handle economies for
3199 	// us and will do so on load too. To make the order at which we are loaded
3200 	// not a factor, we do not overwrite the economy they might have set for us
3201 	// already.
3202 	if (PlayerImmovable* const location = worker.location_.get(egbase())) {
3203 		worker.set_economy(location->get_economy(wwWARE), wwWARE);
3204 		worker.set_economy(location->get_economy(wwWORKER), wwWORKER);
3205 	}
3206 }
3207 
get_task(const std::string & name)3208 const Bob::Task* Worker::Loader::get_task(const std::string& name) {
3209 	if (name == "program")
3210 		return &taskProgram;
3211 	if (name == "transfer")
3212 		return &taskTransfer;
3213 	if (name == "shipping")
3214 		return &taskShipping;
3215 	if (name == "buildingwork")
3216 		return &taskBuildingwork;
3217 	if (name == "return")
3218 		return &taskReturn;
3219 	if (name == "gowarehouse")
3220 		return &taskGowarehouse;
3221 	if (name == "dropoff")
3222 		return &taskDropoff;
3223 	if (name == "releaserecruit")
3224 		return &taskReleaserecruit;
3225 	if (name == "fetchfromflag")
3226 		return &taskFetchfromflag;
3227 	if (name == "waitforcapacity")
3228 		return &taskWaitforcapacity;
3229 	if (name == "leavebuilding")
3230 		return &taskLeavebuilding;
3231 	if (name == "fugitive")
3232 		return &taskFugitive;
3233 	if (name == "geologist")
3234 		return &taskGeologist;
3235 	if (name == "scout")
3236 		return &taskScout;
3237 	return Bob::Loader::get_task(name);
3238 }
3239 
get_program(const std::string & name)3240 const MapObjectProgram* Worker::Loader::get_program(const std::string& name) {
3241 	Worker& worker = get<Worker>();
3242 	return worker.descr().get_program(name);
3243 }
3244 
create_loader()3245 Worker::Loader* Worker::create_loader() {
3246 	return new Loader;
3247 }
3248 
3249 /**
3250  * Load function for all classes derived from \ref Worker
3251  *
3252  * Derived classes must override \ref create_loader to make sure
3253  * the appropriate actual load functions are called.
3254  */
load(EditorGameBase & egbase,MapObjectLoader & mol,FileRead & fr,const TribesLegacyLookupTable & lookup_table,uint8_t packet_version)3255 MapObject::Loader* Worker::load(EditorGameBase& egbase,
3256                                 MapObjectLoader& mol,
3257                                 FileRead& fr,
3258                                 const TribesLegacyLookupTable& lookup_table,
3259                                 uint8_t packet_version) {
3260 	try {
3261 		// header has already been read by caller
3262 		// Some maps contain worker info, so we need compatibility here.
3263 		if (packet_version == 1) {
3264 			fr.c_string();  // Consume tribe name
3265 		}
3266 		const std::string name = lookup_table.lookup_worker(fr.c_string());
3267 
3268 		const WorkerDescr* descr =
3269 		   egbase.tribes().get_worker_descr(egbase.tribes().safe_worker_index(name));
3270 
3271 		Worker* worker = static_cast<Worker*>(&descr->create_object());
3272 		std::unique_ptr<Loader> loader(worker->create_loader());
3273 		loader->init(egbase, mol, *worker);
3274 		loader->load(fr);
3275 		return loader.release();
3276 	} catch (const std::exception& e) {
3277 		throw wexception("loading worker: %s", e.what());
3278 	}
3279 }
3280 
3281 /**
3282  * Save the \ref Worker specific header and version info.
3283  *
3284  * \warning Do not override this function, override \ref do_save instead.
3285  */
save(EditorGameBase & egbase,MapObjectSaver & mos,FileWrite & fw)3286 void Worker::save(EditorGameBase& egbase, MapObjectSaver& mos, FileWrite& fw) {
3287 	fw.unsigned_8(HeaderWorker);
3288 	fw.c_string(descr().name());
3289 
3290 	do_save(egbase, mos, fw);
3291 }
3292 
3293 /**
3294  * Save the data fields of this worker.
3295  *
3296  * This is separate from \ref save because of the way data headers are treated.
3297  *
3298  * Override this function in derived classes.
3299  */
do_save(EditorGameBase & egbase,MapObjectSaver & mos,FileWrite & fw)3300 void Worker::do_save(EditorGameBase& egbase, MapObjectSaver& mos, FileWrite& fw) {
3301 	Bob::save(egbase, mos, fw);
3302 
3303 	fw.unsigned_8(kCurrentPacketVersion);
3304 	fw.unsigned_32(mos.get_object_file_index_or_zero(location_.get(egbase)));
3305 	fw.unsigned_32(mos.get_object_file_index_or_zero(carried_ware_.get(egbase)));
3306 	fw.signed_32(current_exp_);
3307 
3308 	if (transfer_) {
3309 		fw.unsigned_8(1);
3310 		transfer_->write(mos, fw);
3311 	} else {
3312 		fw.unsigned_8(0);
3313 	}
3314 
3315 	fw.unsigned_8(scouts_worklist.size());
3316 	for (auto p : scouts_worklist) {
3317 		if (p.randomwalk) {
3318 			fw.unsigned_8(1);
3319 		} else {
3320 			fw.unsigned_8(0);
3321 			// Is there a better way to save Coords? This makes
3322 			// unnecessary assumptions of the internals of Coords
3323 			fw.signed_16(p.scoutme.x);
3324 			fw.signed_16(p.scoutme.y);
3325 		}
3326 	}
3327 }
3328 }  // namespace Widelands
3329