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/bob.h"
21
22 #include "base/macros.h"
23 #include "base/math.h"
24 #include "base/wexception.h"
25 #include "economy/roadbase.h"
26 #include "economy/route.h"
27 #include "economy/transfer.h"
28 #include "graphic/rendertarget.h"
29 #include "io/fileread.h"
30 #include "io/filewrite.h"
31 #include "logic/game.h"
32 #include "logic/game_data_error.h"
33 #include "logic/map_objects/backtrace.h"
34 #include "logic/map_objects/checkstep.h"
35 #include "logic/map_objects/findbob.h"
36 #include "logic/map_objects/tribes/ship.h"
37 #include "logic/map_objects/tribes/soldier.h"
38 #include "logic/map_objects/tribes/tribe_descr.h"
39 #include "logic/map_objects/world/critter.h"
40 #include "logic/path.h"
41 #include "logic/player.h"
42 #include "logic/widelands_geometry_io.h"
43 #include "map_io/map_object_loader.h"
44 #include "map_io/map_object_saver.h"
45 #include "wui/mapviewpixelconstants.h"
46
47 namespace Widelands {
48
BobDescr(const std::string & init_descname,const MapObjectType init_type,MapObjectDescr::OwnerType owner_type,const LuaTable & table)49 BobDescr::BobDescr(const std::string& init_descname,
50 const MapObjectType init_type,
51 MapObjectDescr::OwnerType owner_type,
52 const LuaTable& table)
53 : MapObjectDescr(init_type, table.get_string("name"), init_descname, table),
54 owner_type_(owner_type),
55 // Only tribe bobs have a vision range, since it would be irrelevant for world bobs.
56 vision_range_(owner_type == MapObjectDescr::OwnerType::kTribe ? table.get_int("vision_range") :
57 0) {
58 if (!is_animation_known("idle")) {
59 throw GameDataError("Bob %s has no idle animation", table.get_string("name").c_str());
60 }
61 }
62
63 /**
64 * Only tribe bobs have a vision range, since it would be irrelevant
65 * for world bobs.
66 *
67 * \returns radius (in fields) of area that the bob can see
68 */
vision_range() const69 uint32_t BobDescr::vision_range() const {
70 return vision_range_;
71 }
72
73 /**
74 * Create a bob of this type
75 */
create(EditorGameBase & egbase,Player * const owner,const Coords & coords) const76 Bob& BobDescr::create(EditorGameBase& egbase, Player* const owner, const Coords& coords) const {
77 Bob& bob = create_object();
78 bob.set_owner(owner);
79 bob.set_position(egbase, coords);
80 bob.init(egbase);
81
82 return bob;
83 }
84
Bob(const BobDescr & init_descr)85 Bob::Bob(const BobDescr& init_descr)
86 : MapObject(&init_descr),
87 position_(FCoords(Coords(0, 0), nullptr)), // not linked anywhere
88 linknext_(nullptr),
89 linkpprev_(nullptr),
90 anim_(0),
91 animstart_(0),
92 walking_(IDLE),
93 walkstart_(0),
94 walkend_(0),
95 actid_(0),
96 actscheduled_(false),
97 in_act_(false) {
98 }
99
100 /**
101 * Cleanup an object. Removes map links
102 */
~Bob()103 Bob::~Bob() {
104 if (position_.field) {
105 molog("MapObject::~MapObject: pos_.field != 0, cleanup() not "
106 "called!\n");
107 abort();
108 }
109 }
110
111 /**
112 * Initialize the object
113 *
114 * \note Make sure you call this from derived classes!
115 */
init(EditorGameBase & egbase)116 bool Bob::init(EditorGameBase& egbase) {
117 MapObject::init(egbase);
118
119 if (upcast(Game, game, &egbase)) {
120 schedule_act(*game, 1);
121 } else {
122 // In editor: play idle task forever
123 set_animation(egbase, descr().get_animation("idle", this));
124 }
125 return true;
126 }
127
128 /**
129 * Perform independent cleanup as necessary.
130 */
cleanup(EditorGameBase & egbase)131 void Bob::cleanup(EditorGameBase& egbase) {
132 while (!stack_.empty()) { // bobs in the editor do not have tasks
133 do_pop_task(dynamic_cast<Game&>(egbase));
134 }
135
136 set_owner(nullptr); // implicitly remove ourselves from owner's map
137
138 if (position_.field) {
139 position_.field = nullptr;
140 *linkpprev_ = linknext_;
141 if (linknext_)
142 linknext_->linkpprev_ = linkpprev_;
143 }
144
145 MapObject::cleanup(egbase);
146 }
147
148 /**
149 * Called by Cmd_Queue when a CMD_ACT event is triggered.
150 * Hand the acting over to the task.
151 *
152 * Change to the next task if necessary.
153 */
act(Game & game,uint32_t const data)154 void Bob::act(Game& game, uint32_t const data) {
155 // Eliminate spurious calls of act().
156 // These calls are to be expected and perfectly normal, e.g. when a carrier's
157 // idle task is interrupted by the request to pick up a ware from a flag.
158 if (data != actid_)
159 return;
160
161 ++actid_;
162 actscheduled_ = false;
163
164 if (stack_.empty()) {
165 signal_ = "";
166 init_auto_task(game);
167
168 if (stack_.empty())
169 throw wexception("MO(%u): init_auto_task() failed to set a task", serial());
170 if (!actscheduled_)
171 throw wexception("MO(%u): init_auto_task() failed to schedule something", serial());
172
173 return;
174 }
175
176 do_act(game);
177 }
178
179 /**
180 * Perform the actual call to update() as appropriate.
181 */
do_act(Game & game)182 void Bob::do_act(Game& game) {
183 assert(!in_act_);
184 assert(stack_.size());
185
186 in_act_ = true;
187
188 const Task& task = *top_state().task;
189
190 (this->*task.update)(game, top_state());
191
192 if (!actscheduled_)
193 throw wexception("MO(%u): update[%s] failed to act", serial(), task.name);
194
195 in_act_ = false;
196 }
197
198 /**
199 * Kill self ASAP.
200 */
schedule_destroy(Game & game)201 void Bob::schedule_destroy(Game& game) {
202 MapObject::schedule_destroy(game);
203 ++actid_; // to skip over any updates that may be scheduled
204 actscheduled_ = true;
205 }
206
207 /**
208 * Schedule a new act for the current task. All other pending acts are
209 * cancelled.
210 */
schedule_act(Game & game,uint32_t tdelta)211 void Bob::schedule_act(Game& game, uint32_t tdelta) {
212 MapObject::schedule_act(game, tdelta, actid_);
213 actscheduled_ = true;
214 }
215
216 /**
217 * Explicitly state that we don't want to act.
218 */
skip_act()219 void Bob::skip_act() {
220 assert(in_act_);
221
222 actscheduled_ = true;
223 }
224
225 /**
226 * Push a new task onto the stack.
227 *
228 * push_task() itself does not call any functions of the task, so the caller
229 * can fill the state information with parameters for the task.
230 */
push_task(Game & game,const Task & task,uint32_t const tdelta)231 void Bob::push_task(Game& game, const Task& task, uint32_t const tdelta) {
232 assert(!task.unique || !get_state(task));
233 assert(in_act_ || stack_.empty());
234
235 stack_.push_back(State(&task));
236 schedule_act(game, tdelta);
237 }
238
239 /**
240 * Actually pop the top-most task, but don't schedule anything.
241 */
do_pop_task(Game & game)242 void Bob::do_pop_task(Game& game) {
243 State& state = top_state();
244
245 if (state.task->pop)
246 (this->*state.task->pop)(game, state);
247
248 delete state.path;
249 delete state.route;
250
251 stack_.pop_back();
252 }
253
254 /**
255 * Remove the current task from the stack.
256 *
257 * pop_task() itself does not call any parent task functions, but it ensures
258 * that it will happen.
259 */
pop_task(Game & game)260 void Bob::pop_task(Game& game) {
261 assert(in_act_);
262
263 do_pop_task(game);
264
265 schedule_act(game, 10);
266 }
267
268 /**
269 * Get the bottom-most (usually the only) state of this task from the stack.
270 * \return 0 if this task is not running at all.
271 */
get_state(const Task & task)272 Bob::State* Bob::get_state(const Task& task) {
273 std::vector<State>::iterator it = stack_.end();
274
275 while (it != stack_.begin()) {
276 --it;
277
278 if (it->task == &task)
279 return &*it;
280 }
281
282 return nullptr;
283 }
284
get_state(const Task & task) const285 Bob::State const* Bob::get_state(const Task& task) const {
286 std::vector<State>::const_iterator it = stack_.end();
287
288 while (it != stack_.begin()) {
289 --it;
290
291 if (it->task == &task)
292 return &*it;
293 }
294
295 return nullptr;
296 }
297
298 /**
299 * Mark the current signal as handled.
300 */
signal_handled()301 void Bob::signal_handled() {
302 assert(in_act_);
303
304 signal_.clear();
305 }
306
307 /**
308 * Send the given signal to this bob.
309 *
310 * Signal delivery is asynchronous, i.e. this functions guarantees that
311 * the top-most task's update() function is called, but only with a certain
312 * delay.
313 *
314 * This function also calls all tasks' signal_immediate() function immediately.
315 *
316 * \param g the \ref Game object
317 * \param sig the signal string
318 */
send_signal(Game & game,char const * const sig)319 void Bob::send_signal(Game& game, char const* const sig) {
320 assert(*sig); // use set_signal() for signal removal
321
322 for (uint32_t i = 0; i < stack_.size(); ++i) {
323 State& state = stack_[i];
324
325 if (state.task->signal_immediate)
326 (this->*state.task->signal_immediate)(game, state, sig);
327 }
328
329 signal_ = sig;
330 schedule_act(game, 10);
331 }
332
333 /**
334 * Force a complete reset of the state stack.
335 *
336 * The state stack is emptied completely, and an init auto task is scheduled
337 * as if the Bob has just been created and initialized.
338 */
reset_tasks(Game & game)339 void Bob::reset_tasks(Game& game) {
340 while (!stack_.empty())
341 do_pop_task(game);
342
343 signal_.clear();
344
345 ++actid_;
346 schedule_act(game, 10);
347 }
348
349 /**
350 * Wait a time or indefinitely.
351 *
352 * Every signal can interrupt this task. No signals are caught.
353 */
354
355 Bob::Task const Bob::taskIdle = {"idle", &Bob::idle_update,
356 nullptr, // signal_immediate
357 nullptr, true};
358
359 /**
360 * Start an idle phase, using the given animation
361 *
362 * If the timeout is a positive value, the idle phase stops after the given
363 * time.
364 *
365 * This task always succeeds unless interrupted.
366 */
start_task_idle(Game & game,uint32_t const anim,int32_t const timeout)367 void Bob::start_task_idle(Game& game, uint32_t const anim, int32_t const timeout) {
368 assert(timeout < 0 || timeout > 0);
369
370 set_animation(game, anim);
371
372 push_task(game, taskIdle);
373
374 top_state().ivar1 = timeout;
375 }
376
idle_update(Game & game,State & state)377 void Bob::idle_update(Game& game, State& state) {
378 if (!state.ivar1 || get_signal().size())
379 return pop_task(game);
380
381 if (state.ivar1 > 0)
382 schedule_act(game, state.ivar1);
383 else
384 skip_act();
385
386 state.ivar1 = 0;
387 }
388
is_idle()389 bool Bob::is_idle() {
390 return get_state(taskIdle);
391 }
392
393 /**
394 * Move along a predefined path.
395 * \par ivar1 the step number.
396 * \par ivar2 non-zero if we should force moving onto the final field.
397 * \par ivar3 number of steps to take maximally or -1
398 *
399 * Sets the following signal(s):
400 * "fail" - cannot move along the given path
401 */
402 Bob::Task const Bob::taskMovepath = {"movepath", &Bob::movepath_update,
403 nullptr, // signal_immediate
404 nullptr, true};
405
406 struct BlockedTracker {
407 struct CoordData {
408 Coords coord;
409 int dist;
410 };
411 // Distance-based ordering as a heuristic for unblock()
412 struct CoordOrdering {
operator ()Widelands::BlockedTracker::CoordOrdering413 bool operator()(const CoordData& a, const CoordData& b) const {
414 if (a.dist != b.dist)
415 return a.dist < b.dist;
416 return std::forward_as_tuple(a.coord.y, a.coord.x) <
417 std::forward_as_tuple(b.coord.y, b.coord.x);
418 }
419 };
420 using Cache = std::map<CoordData, bool, CoordOrdering>;
421
BlockedTrackerWidelands::BlockedTracker422 BlockedTracker(Game& game, Bob& bob, const Coords& finaldest)
423 : game_(game), bob_(bob), map_(game.map()), finaldest_(finaldest) {
424 nrblocked_ = 0;
425 disabled_ = false;
426 }
427
428 // This heuristic tries to unblock fields that are close to the destination,
429 // in the hope that subsequent pathfinding will find a way to bring us
430 // closer, if not complete to, the destination
unblockWidelands::BlockedTracker431 void unblock() {
432 uint32_t origblocked = nrblocked_;
433 int unblockprob = nrblocked_;
434
435 for (Cache::iterator it = nodes_.begin(); it != nodes_.end() && unblockprob > 0; ++it) {
436 if (it->second) {
437 if (static_cast<int32_t>(game_.logic_rand() % origblocked) < unblockprob) {
438 it->second = false;
439 --nrblocked_;
440 unblockprob -= 2;
441 }
442 }
443 }
444 }
445
is_blockedWidelands::BlockedTracker446 bool is_blocked(const FCoords& field) {
447 if (disabled_)
448 return false;
449
450 CoordData cd;
451 cd.coord = field;
452 cd.dist = map_.calc_distance(field, finaldest_);
453
454 Cache::iterator it = nodes_.find(cd);
455 if (it != nodes_.end())
456 return it->second;
457
458 bool const blocked = bob_.check_node_blocked(game_, field, false);
459 nodes_.insert(std::make_pair(cd, blocked));
460 if (blocked)
461 ++nrblocked_;
462 return blocked;
463 }
464
465 Game& game_;
466 Bob& bob_;
467 const Map& map_;
468 Coords finaldest_;
469 Cache nodes_;
470 int nrblocked_;
471 bool disabled_;
472 };
473
474 struct CheckStepBlocked {
CheckStepBlockedWidelands::CheckStepBlocked475 explicit CheckStepBlocked(BlockedTracker& tracker) : tracker_(tracker) {
476 }
477
allowedWidelands::CheckStepBlocked478 bool allowed(const Map&, FCoords, FCoords end, int32_t, CheckStep::StepId) const {
479 if (end == tracker_.finaldest_)
480 return true;
481
482 return !tracker_.is_blocked(end);
483 }
reachable_destWidelands::CheckStepBlocked484 bool reachable_dest(const Map&, FCoords) const {
485 return true;
486 }
487
488 BlockedTracker& tracker_;
489 };
490
491 /**
492 * Start moving to the given destination. persist is the same parameter as
493 * for Map::findpath().
494 *
495 * \return false if no path could be found.
496 *
497 * \note The task finishes once the goal has been reached. It may fail.
498 *
499 * \param only_step defines how many steps should be taken, before this
500 * returns as a success
501 */
start_task_movepath(Game & game,const Coords & dest,int32_t const persist,const DirAnimations & anims,bool const forceonlast,int32_t const only_step,bool const forceall)502 bool Bob::start_task_movepath(Game& game,
503 const Coords& dest,
504 int32_t const persist,
505 const DirAnimations& anims,
506 bool const forceonlast,
507 int32_t const only_step,
508 bool const forceall) {
509 Path path;
510 BlockedTracker tracker(game, *this, dest);
511 CheckStepAnd cstep;
512
513 if (forceonlast)
514 cstep.add(CheckStepWalkOn(descr().movecaps(), true));
515 else
516 cstep.add(CheckStepDefault(descr().movecaps()));
517 cstep.add(CheckStepBlocked(tracker));
518
519 if (forceall)
520 tracker.disabled_ = true;
521
522 const Map& map = game.map();
523 if (map.findpath(position_, dest, persist, path, cstep) < 0) {
524 if (!tracker.nrblocked_)
525 return false;
526
527 tracker.unblock();
528 if (map.findpath(position_, dest, persist, path, cstep) < 0) {
529 if (!tracker.nrblocked_)
530 return false;
531
532 tracker.disabled_ = true;
533 if (map.findpath(position_, dest, persist, path, cstep) < 0)
534 return false;
535 }
536 }
537
538 push_task(game, taskMovepath);
539 State& state = top_state();
540 state.path = new Path(path);
541 state.ivar1 = 0; // step #
542 state.ivar2 = forceonlast ? 1 : (forceall ? 2 : 0);
543 state.ivar3 = only_step;
544 state.diranims = anims;
545 return true;
546 }
547
548 /**
549 * Start moving along the given, precalculated path.
550 */
start_task_movepath(Game & game,const Path & path,const DirAnimations & anims,bool const forceonlast,int32_t const only_step)551 void Bob::start_task_movepath(Game& game,
552 const Path& path,
553 const DirAnimations& anims,
554 bool const forceonlast,
555 int32_t const only_step) {
556 assert(path.get_start() == get_position());
557
558 push_task(game, taskMovepath);
559 State& state = top_state();
560 state.path = new Path(path);
561 state.ivar1 = 0;
562 state.ivar2 = forceonlast ? 1 : 0;
563 state.ivar3 = only_step;
564 state.diranims = anims;
565 }
566
567 /**
568 * Move to the given index on the given path. The current position must be on
569 * the given path.
570 *
571 * \return true if a task has been started, or false if we already are on
572 * the given path index.
573 */
start_task_movepath(Game & game,const Path & origpath,int32_t const index,const DirAnimations & anims,bool const forceonlast,int32_t const only_step)574 bool Bob::start_task_movepath(Game& game,
575 const Path& origpath,
576 int32_t const index,
577 const DirAnimations& anims,
578 bool const forceonlast,
579 int32_t const only_step) {
580 CoordPath path(game.map(), origpath);
581 int32_t const curidx = path.get_index(get_position());
582
583 if (curidx == -1) {
584 molog("ERROR: (%i, %i) is not on the given path:\n", get_position().x, get_position().y);
585 for (const Coords& coords : path.get_coords()) {
586 molog("* (%i, %i)\n", coords.x, coords.y);
587 }
588 log_general_info(game);
589 log("%s", get_backtrace().c_str());
590 throw wexception("MO(%u): start_task_movepath(index): not on path", serial());
591 }
592
593 if (curidx != index) {
594 if (curidx < index) {
595 path.truncate(index);
596 path.trim_start(curidx);
597 } else {
598 path.truncate(curidx);
599 path.trim_start(index);
600 path.reverse();
601 }
602 start_task_movepath(game, path, anims, forceonlast, only_step);
603 return true;
604 }
605
606 return false;
607 }
608
movepath_update(Game & game,State & state)609 void Bob::movepath_update(Game& game, State& state) {
610 if (get_signal().size()) {
611 return pop_task(game);
612 }
613
614 assert(state.ivar1 >= 0);
615 Path const* const path = state.path;
616
617 if (!path)
618 // probably success; this can happen when loading a game
619 // that contains a zero-length path.
620 return pop_task(game);
621
622 if (static_cast<Path::StepVector::size_type>(state.ivar1) >= path->get_nsteps()) {
623 assert(position_ == path->get_end());
624 return pop_task(game); // success
625 } else if (state.ivar1 == state.ivar3)
626 // We have stepped all steps that we were asked for.
627 // This is some kind of success, though we do not are were we wanted
628 // to go
629 return pop_task(game);
630
631 Direction const dir = (*path)[state.ivar1];
632
633 // Slowing down a ship if two or more on same spot
634 // Using probability of 1/8 and pausing it for 5, 10 or 15 seconds
635 if (game.logic_rand() % 8 == 0) {
636 if (is_a(Ship, this)) {
637 const uint32_t ships_count = game.map().find_bobs(
638 game, Widelands::Area<Widelands::FCoords>(get_position(), 0), nullptr, FindBobShip());
639 assert(ships_count > 0);
640 if (ships_count > 1) {
641 molog("Pausing the ship because %d ships on the same spot\n", ships_count);
642 return start_task_idle(
643 game, state.diranims.get_animation(dir), ((game.logic_rand() % 3) + 1) * 5000);
644 }
645 }
646 }
647
648 bool forcemove =
649 (state.ivar2 &&
650 static_cast<Path::StepVector::size_type>(state.ivar1) + 1 == path->get_nsteps());
651
652 ++state.ivar1;
653 return start_task_move(game, dir, state.diranims, state.ivar2 == 2 ? true : forcemove);
654 // Note: state pointer is invalid beyond this point
655 }
656
657 /**
658 * Move into one direction for one step.
659 * \li ivar1: direction
660 * \li ivar2: non-zero if the move should be forced
661 */
662 Bob::Task const Bob::taskMove = {"move", &Bob::move_update, nullptr, nullptr, true};
663
664 /**
665 * Move into the given direction, without passability checks.
666 */
start_task_move(Game & game,int32_t const dir,const DirAnimations & anims,bool const forcemove)667 void Bob::start_task_move(Game& game,
668 int32_t const dir,
669 const DirAnimations& anims,
670 bool const forcemove) {
671 int32_t const tdelta =
672 start_walk(game, static_cast<WalkingDir>(dir), anims.get_animation(dir), forcemove);
673 if (tdelta < 0)
674 return send_signal(game, tdelta == -2 ? "blocked" : "fail");
675 push_task(game, taskMove, tdelta);
676 }
677
move_update(Game & game,State &)678 void Bob::move_update(Game& game, State&) {
679 if (static_cast<uint32_t>(walkend_) <= game.get_gametime()) {
680 end_walk();
681 return pop_task(game);
682 } else
683 // Only end the task once we've actually completed the step
684 // Ignore signals until then
685 return schedule_act(game, walkend_ - game.get_gametime());
686 }
687
688 // Calculates the actual position to draw on from the base node position. This
689 // function takes walking etc. into account.
690 //
691 // pos is the location, in pixels, of the node position_ on screen with scale
692 // and height taken into account.
calc_drawpos(const EditorGameBase & game,const Vector2f & field_on_dst,const float scale) const693 Vector2f Bob::calc_drawpos(const EditorGameBase& game,
694 const Vector2f& field_on_dst,
695 const float scale) const {
696 const Map& map = game.map();
697 const FCoords end = position_;
698 FCoords start;
699 Vector2f spos = field_on_dst;
700 Vector2f epos = field_on_dst;
701
702 const float triangle_w = kTriangleWidth * scale;
703 const float triangle_h = kTriangleHeight * scale;
704
705 bool bridge = false;
706 switch (walking_) {
707 case WALK_NW:
708 map.get_brn(end, &start);
709 spos.x += triangle_w / 2.f;
710 spos.y += triangle_h;
711 bridge = is_bridge_segment(end.field->road_southeast);
712 break;
713 case WALK_NE:
714 map.get_bln(end, &start);
715 spos.x -= triangle_w / 2.f;
716 spos.y += triangle_h;
717 bridge = is_bridge_segment(end.field->road_southwest);
718 break;
719 case WALK_W:
720 map.get_rn(end, &start);
721 spos.x += triangle_w;
722 bridge = is_bridge_segment(end.field->road_east);
723 break;
724 case WALK_E:
725 map.get_ln(end, &start);
726 spos.x -= triangle_w;
727 bridge = is_bridge_segment(start.field->road_east);
728 break;
729 case WALK_SW:
730 map.get_trn(end, &start);
731 spos.x += triangle_w / 2.f;
732 spos.y -= triangle_h;
733 bridge = is_bridge_segment(start.field->road_southwest);
734 break;
735 case WALK_SE:
736 map.get_tln(end, &start);
737 spos.x -= triangle_w / 2.f;
738 spos.y -= triangle_h;
739 bridge = is_bridge_segment(start.field->road_southeast);
740 break;
741
742 case IDLE:
743 start.field = nullptr;
744 break;
745 }
746
747 if (start.field) {
748 spos.y += end.field->get_height() * kHeightFactor * scale;
749 spos.y -= start.field->get_height() * kHeightFactor * scale;
750
751 assert(static_cast<uint32_t>(walkstart_) <= game.get_gametime());
752 assert(walkstart_ < walkend_);
753 const float f = math::clamp(
754 static_cast<float>(game.get_gametime() - walkstart_) / (walkend_ - walkstart_), 0.f, 1.f);
755 epos.x = f * epos.x + (1.f - f) * spos.x;
756 epos.y = f * epos.y + (1.f - f) * spos.y;
757 if (bridge) {
758 epos.y -= game.player(end.field->get_owned_by()).tribe().bridge_height() * scale *
759 (1 - 4 * (f - 0.5f) * (f - 0.5f));
760 }
761 }
762 return epos;
763 }
764
765 /// It LERPs between start and end position when we are walking.
766 /// Note that the current node is actually the node that we are walking to, not
767 /// the the one that we start from.
draw(const EditorGameBase & egbase,const InfoToDraw &,const Vector2f & field_on_dst,const Widelands::Coords & coords,const float scale,RenderTarget * dst) const768 void Bob::draw(const EditorGameBase& egbase,
769 const InfoToDraw&,
770 const Vector2f& field_on_dst,
771 const Widelands::Coords& coords,
772 const float scale,
773 RenderTarget* dst) const {
774 if (!anim_) {
775 return;
776 }
777
778 auto* const bob_owner = get_owner();
779 const Vector2f point_on_dst = calc_drawpos(egbase, field_on_dst, scale);
780 dst->blit_animation(point_on_dst, coords, scale, anim_, egbase.get_gametime() - animstart_,
781 (bob_owner == nullptr) ? nullptr : &bob_owner->get_playercolor());
782 }
783
784 /**
785 * Set a looping animation, starting now.
786 */
set_animation(EditorGameBase & egbase,uint32_t const anim)787 void Bob::set_animation(EditorGameBase& egbase, uint32_t const anim) {
788 anim_ = anim;
789 animstart_ = egbase.get_gametime();
790 }
791
792 /**
793 * Cause the object to walk, honoring passable/unwalkable parts of the map
794 * using movecaps. If force is true, the passability check is skipped.
795 *
796 * \return the number of milliseconds after which the walk has ended. You must
797 * call end_walk() after this time, so schedule a task_act(). Returns -1
798 * if the step is forbidden, and -2 if it is currently blocked.
799 */
start_walk(Game & game,WalkingDir const dir,uint32_t const a,bool const force)800 int32_t Bob::start_walk(Game& game, WalkingDir const dir, uint32_t const a, bool const force) {
801 FCoords newnode;
802
803 const Map& map = game.map();
804 map.get_neighbour(position_, dir, &newnode);
805
806 // Move capability check
807 if (!force) {
808 CheckStepDefault cstep(descr().movecaps());
809
810 if (!cstep.allowed(map, position_, newnode, dir, CheckStep::stepNormal))
811 return -1;
812 }
813
814 // Always call check_node_blocked, because it might communicate with other
815 // bobs (as is the case for soldiers on the battlefield).
816 if (check_node_blocked(game, newnode, true) && !force)
817 return -2;
818
819 // Move is go
820 int32_t const tdelta = map.calc_cost(position_, dir);
821 assert(tdelta);
822
823 walking_ = dir;
824 walkstart_ = game.get_gametime();
825 walkend_ = walkstart_ + tdelta;
826
827 set_position(game, newnode);
828 set_animation(game, a);
829
830 return tdelta; // yep, we were successful
831 }
832
check_node_blocked(Game & game,const FCoords & field,bool)833 bool Bob::check_node_blocked(Game& game, const FCoords& field, bool) {
834 // Battles always block movement!
835 std::vector<Bob*> soldiers;
836 game.map().find_bobs(game, Area<FCoords>(field, 0), &soldiers, FindBobEnemySoldier(get_owner()));
837
838 if (!soldiers.empty()) {
839 for (Bob* temp_bob : soldiers) {
840 upcast(Soldier, soldier, temp_bob);
841 if (soldier->get_battle())
842 return true;
843 }
844 }
845
846 return false;
847 }
848
849 /**
850 * Give the bob a new owner.
851 *
852 * This will update the owner's viewing area.
853 */
set_owner(Player * const player)854 void Bob::set_owner(Player* const player) {
855 if (owner_ && position_.field)
856 owner_->unsee_area(Area<FCoords>(get_position(), descr().vision_range()));
857
858 owner_ = player;
859
860 if (owner_ != nullptr && position_.field)
861 owner_->see_area(Area<FCoords>(get_position(), descr().vision_range()));
862 }
863
864 /**
865 * Move the bob to a new position.
866 *
867 * Performs the necessary (un)linking in the \ref Field structures and
868 * updates the owner's viewing area, if the bob has an owner.
869 */
set_position(EditorGameBase & egbase,const Coords & coords)870 void Bob::set_position(EditorGameBase& egbase, const Coords& coords) {
871 FCoords oldposition = position_;
872
873 if (position_.field) {
874 *linkpprev_ = linknext_;
875 if (linknext_)
876 linknext_->linkpprev_ = linkpprev_;
877 }
878
879 position_ = egbase.map().get_fcoords(coords);
880
881 linknext_ = position_.field->bobs;
882 linkpprev_ = &position_.field->bobs;
883 if (linknext_)
884 linknext_->linkpprev_ = &linknext_;
885 *linkpprev_ = this;
886
887 if (owner_ != nullptr) {
888 owner_->see_area(Area<FCoords>(get_position(), descr().vision_range()));
889
890 if (oldposition.field)
891 owner_->unsee_area(Area<FCoords>(oldposition, descr().vision_range()));
892 }
893
894 // Since pretty much everything in Widelands eventually results in the
895 // movement of a worker (e.g. transporting wares etc.), this should
896 // help us to find desyncs pretty rapidly.
897 // In particular, I wanted to add something to set_position because
898 // it involves coordinates and will thus additionally highlight desyncs
899 // in pathfinding even when two paths have the same length, and in
900 // randomly generated movements.
901 if (upcast(Game, game, &egbase)) {
902 StreamWrite& ss = game->syncstream();
903 ss.unsigned_8(SyncEntry::kBobSetPosition);
904 ss.unsigned_32(serial());
905 ss.signed_16(coords.x);
906 ss.signed_16(coords.y);
907 }
908 }
909
910 /// Give debug information.
log_general_info(const EditorGameBase & egbase) const911 void Bob::log_general_info(const EditorGameBase& egbase) const {
912 FORMAT_WARNINGS_OFF
913 molog("Owner: %p\n", owner_);
914 FORMAT_WARNINGS_ON
915 molog("Postition: (%i, %i)\n", position_.x, position_.y);
916 molog("ActID: %i\n", actid_);
917 molog("ActScheduled: %s\n", actscheduled_ ? "true" : "false");
918 molog("Animation: %s\n", anim_ ? descr().get_animation_name(anim_).c_str() : "\\<none\\>");
919
920 molog("AnimStart: %i\n", animstart_);
921 molog("WalkingDir: %i\n", walking_);
922 molog("WalkingStart: %i\n", walkstart_);
923 molog("WalkEnd: %i\n", walkend_);
924
925 molog("Signal: %s\n", signal_.c_str());
926
927 molog("Stack size: %" PRIuS "\n", stack_.size());
928
929 for (size_t i = 0; i < stack_.size(); ++i) {
930 molog("Stack dump %" PRIuS "/%" PRIuS "\n", i + 1, stack_.size());
931
932 molog("* task->name: %s\n", stack_[i].task->name);
933
934 molog("* ivar1: %i\n", stack_[i].ivar1);
935 molog("* ivar2: %i\n", stack_[i].ivar2);
936 molog("* ivar3: %i\n", stack_[i].ivar3);
937
938 FORMAT_WARNINGS_OFF
939 molog("* object pointer: %p\n", stack_[i].objvar1.get(egbase));
940 FORMAT_WARNINGS_ON
941 molog("* svar1: %s\n", stack_[i].svar1.c_str());
942
943 molog("* coords: (%i, %i)\n", stack_[i].coords.x, stack_[i].coords.y);
944 molog("* diranims:");
945 for (Direction dir = FIRST_DIRECTION; dir <= LAST_DIRECTION; ++dir) {
946 molog(" %d", stack_[i].diranims.get_animation(dir));
947 }
948 FORMAT_WARNINGS_OFF
949 molog("\n* path: %p\n", stack_[i].path);
950 FORMAT_WARNINGS_ON
951 if (stack_[i].path) {
952 const Path& path = *stack_[i].path;
953 molog("** Path length: %" PRIuS "\n", path.get_nsteps());
954 molog("** Start: (%i, %i)\n", path.get_start().x, path.get_start().y);
955 molog("** End: (%i, %i)\n", path.get_end().x, path.get_end().y);
956 // Printing all coordinates of the path
957 CoordPath coordpath(egbase.map(), path);
958 for (const Coords& coords : coordpath.get_coords()) {
959 molog("* (%i, %i)\n", coords.x, coords.y);
960 }
961 }
962 FORMAT_WARNINGS_OFF
963 molog("* route: %p\n", stack_[i].route);
964 molog("* program: %p\n", stack_[i].route);
965 FORMAT_WARNINGS_ON
966 }
967 }
968
969 /*
970 ==============================
971
972 Load/save support
973
974 ==============================
975 */
976
977 constexpr uint8_t kCurrentPacketVersion = 1;
978
Loader()979 Bob::Loader::Loader() {
980 }
981
load(FileRead & fr)982 void Bob::Loader::load(FileRead& fr) {
983 MapObject::Loader::load(fr);
984
985 try {
986 uint8_t packet_version = fr.unsigned_8();
987 if (packet_version == kCurrentPacketVersion) {
988
989 Bob& bob = get<Bob>();
990
991 if (PlayerNumber owner_number = fr.unsigned_8()) {
992 if (owner_number > egbase().map().get_nrplayers())
993 throw GameDataError("owner number is %u but there are only %u players", owner_number,
994 egbase().map().get_nrplayers());
995
996 Player* owner = egbase().get_player(owner_number);
997 if (!owner)
998 throw GameDataError("owning player %u does not exist", owner_number);
999
1000 bob.set_owner(owner);
1001 }
1002
1003 bob.set_position(egbase(), read_coords_32(&fr));
1004
1005 // Animation. If the animation is no longer known, pick the main animation instead.
1006 std::string animname = fr.c_string();
1007 if (animname.empty()) {
1008 bob.anim_ = 0;
1009 } else if (bob.descr().is_animation_known(animname)) {
1010 bob.anim_ = bob.descr().get_animation(animname, &bob);
1011 } else {
1012 bob.anim_ = bob.descr().main_animation();
1013 log("Unknown animation '%s' for bob '%s', using main animation instead.\n",
1014 animname.c_str(), bob.descr().name().c_str());
1015 }
1016
1017 bob.animstart_ = fr.signed_32();
1018 bob.walking_ = static_cast<WalkingDir>(read_direction_8_allow_null(&fr));
1019 if (bob.walking_) {
1020 bob.walkstart_ = fr.signed_32();
1021 bob.walkend_ = fr.signed_32();
1022 }
1023
1024 bob.actid_ = fr.unsigned_32();
1025 bob.signal_ = fr.c_string();
1026
1027 uint32_t stacksize = fr.unsigned_32();
1028 bob.stack_.resize(stacksize);
1029 states.resize(stacksize);
1030 for (uint32_t i = 0; i < stacksize; ++i) {
1031 State& state = bob.stack_[i];
1032 LoadState& loadstate = states[i];
1033
1034 state.task = get_task(fr.c_string());
1035 state.ivar1 = fr.signed_32();
1036 state.ivar2 = fr.signed_32();
1037 state.ivar3 = fr.signed_32();
1038 loadstate.objvar1 = fr.unsigned_32();
1039 state.svar1 = fr.c_string();
1040 state.coords = read_coords_32_allow_null(&fr, egbase().map().extent());
1041
1042 if (fr.unsigned_8()) {
1043 uint32_t anims[6];
1044 for (int j = 0; j < 6; ++j) {
1045 std::string dir_animname = fr.c_string();
1046 if (bob.descr().is_animation_known(dir_animname)) {
1047 anims[j] = bob.descr().get_animation(dir_animname, &bob);
1048 } else {
1049 anims[j] = bob.descr().main_animation();
1050 log("Unknown directional animation '%s' for bob '%s', using main animation "
1051 "instead.\n",
1052 dir_animname.c_str(), bob.descr().name().c_str());
1053 }
1054 }
1055 state.diranims =
1056 DirAnimations(anims[0], anims[1], anims[2], anims[3], anims[4], anims[5]);
1057 }
1058
1059 if (fr.unsigned_8()) {
1060 state.path = new Path;
1061 state.path->load(fr, egbase().map());
1062 }
1063
1064 if (fr.unsigned_8()) {
1065 state.route = new Route;
1066 state.route->load(loadstate.route, fr);
1067 }
1068
1069 std::string programname = fr.c_string();
1070 if (programname.size())
1071 state.program = get_program(programname);
1072 }
1073 } else {
1074 throw UnhandledVersionError("Bob", packet_version, kCurrentPacketVersion);
1075 }
1076 } catch (const WException& e) {
1077 throw wexception("loading bob: %s", e.what());
1078 }
1079 }
1080
load_pointers()1081 void Bob::Loader::load_pointers() {
1082 MapObject::Loader::load_pointers();
1083
1084 Bob& bob = get<Bob>();
1085 for (uint32_t i = 0; i < bob.stack_.size(); ++i) {
1086 State& state = bob.stack_[i];
1087 LoadState& loadstate = states[i];
1088
1089 if (loadstate.objvar1)
1090 state.objvar1 = &mol().get<MapObject>(loadstate.objvar1);
1091
1092 if (state.route)
1093 state.route->load_pointers(loadstate.route, mol());
1094 }
1095 }
1096
load_finish()1097 void Bob::Loader::load_finish() {
1098 MapObject::Loader::load_finish();
1099
1100 // Care about new mapobject saving / loading - map objects don't get a task,
1101 // if created in the editor, so we give them one here.
1102 // See bug #537392 for more information:
1103 // https://bugs.launchpad.net/widelands/+bug/537392
1104 Bob& bob = get<Bob>();
1105 if (bob.stack_.empty() && !egbase().get_gametime())
1106 if (upcast(Game, game, &egbase())) {
1107 bob.init_auto_task(*game);
1108 }
1109 }
1110
get_task(const std::string & name)1111 const Bob::Task* Bob::Loader::get_task(const std::string& name) {
1112 if (name == "move")
1113 return &taskMove;
1114 if (name == "movepath")
1115 return &taskMovepath;
1116 if (name == "idle")
1117 return &taskIdle;
1118
1119 throw GameDataError("unknown bob task '%s'", name.c_str());
1120 }
1121
get_program(const std::string & name)1122 const MapObjectProgram* Bob::Loader::get_program(const std::string& name) {
1123 throw GameDataError("unknown map object program '%s'", name.c_str());
1124 }
1125
save(EditorGameBase & eg,MapObjectSaver & mos,FileWrite & fw)1126 void Bob::save(EditorGameBase& eg, MapObjectSaver& mos, FileWrite& fw) {
1127 MapObject::save(eg, mos, fw);
1128
1129 fw.unsigned_8(kCurrentPacketVersion);
1130
1131 fw.unsigned_8(owner_ ? owner_->player_number() : 0);
1132 write_coords_32(&fw, position_);
1133
1134 // linkprev_ and linknext_ are recreated automatically
1135
1136 fw.c_string(anim_ ? descr().get_animation_name(anim_) : "");
1137 fw.signed_32(animstart_);
1138 write_direction_8_allow_null(&fw, walking_);
1139 if (walking_) {
1140 fw.signed_32(walkstart_);
1141 fw.signed_32(walkend_);
1142 }
1143
1144 fw.unsigned_32(actid_);
1145 fw.c_string(signal_);
1146
1147 fw.unsigned_32(stack_.size());
1148 for (unsigned int i = 0; i < stack_.size(); ++i) {
1149 const State& state = stack_[i];
1150
1151 fw.c_string(state.task->name);
1152 fw.signed_32(state.ivar1);
1153 fw.signed_32(state.ivar2);
1154 fw.signed_32(state.ivar3);
1155 if (const MapObject* obj = state.objvar1.get(eg)) {
1156 fw.unsigned_32(mos.get_object_file_index(*obj));
1157 } else {
1158 fw.unsigned_32(0);
1159 }
1160 fw.c_string(state.svar1);
1161
1162 write_coords_32(&fw, state.coords);
1163
1164 if (state.diranims) {
1165 fw.unsigned_8(1);
1166 for (int dir = 1; dir <= 6; ++dir)
1167 fw.c_string(descr().get_animation_name(state.diranims.get_animation(dir)).c_str());
1168 } else {
1169 fw.unsigned_8(0);
1170 }
1171
1172 if (state.path) {
1173 fw.unsigned_8(1);
1174 state.path->save(fw);
1175 } else {
1176 fw.unsigned_8(0);
1177 }
1178
1179 if (state.route) {
1180 fw.unsigned_8(1);
1181 state.route->save(fw, eg, mos);
1182 } else {
1183 fw.unsigned_8(0);
1184 }
1185
1186 fw.c_string(state.program ? state.program->name() : "");
1187 }
1188 }
1189 } // namespace Widelands
1190