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