~marten-r/widelands/feature-3d-rendering-open-gl-es-compat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
/*
 * Copyright (C) 2002-2004, 2006-2013 by the Widelands Development Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include "logic/carrier.h"

#include "economy/flag.h"
#include "economy/road.h"
#include "economy/ware_instance.h"
#include "logic/game.h"
#include "logic/game_data_error.h"
#include "upcast.h"
#include "wexception.h"

namespace Widelands {

/**
 * Signal "road" on road split.
 * Signal "ware" when a ware has arrived.
 */
Bob::Task const Carrier::taskRoad = {
	"road",
	static_cast<Bob::Ptr>(&Carrier::road_update),
	0,
	static_cast<Bob::Ptr>(&Carrier::road_pop),
	true
};


/**
 * Work on the given road, assume the location is correct.
*/
void Carrier::start_task_road(Game & game)
{
	push_task(game, taskRoad);

	top_state().ivar1 = 0;

	m_promised_pickup_to = NOONE;
}


/**
 * Called by Road code when the road is split.
*/
void Carrier::update_task_road(Game & game)
{
	send_signal(game, "road");
}


void Carrier::road_update(Game & game, State & state)
{
	std::string signal = get_signal();

	if (signal == "road" || signal == "ware") {
		// The road changed under us or we're supposed to pick up some ware
		signal_handled();
	} else if (signal == "blocked") {
		// Blocked by an ongoing battle
		signal_handled();
		set_animation(game, descr().get_animation("idle"));
		return schedule_act(game, 250);
	} else if (signal.size()) {
		// Something else happened (probably a location signal)
		molog("[road]: Terminated by signal '%s'\n", signal.c_str());
		return pop_task(game);
	}

	Road & road = ref_cast<Road, PlayerImmovable>(*get_location(game));

	// Check for pending items
	if (m_promised_pickup_to == NOONE)
		find_pending_item(game);

	if (m_promised_pickup_to != NOONE) {
		if (state.ivar1) {
			state.ivar1 = 0;
			return start_task_transport(game, m_promised_pickup_to);
		} else {
			// Short delay before we move to pick up
			state.ivar1 = 1;

			set_animation(game, descr().get_animation("idle"));
			return schedule_act(game, 50);
		}
	}

	// Move into idle position if necessary
	if
		(start_task_movepath
		 	(game,
		 	 road.get_path(),
		 	 road.get_idle_index(),
		 	 descr().get_right_walk_anims(does_carry_ware()),false))
		return;

	// Be bored. There's nothing good on TV, either.
	// TODO: idle animations
	set_animation(game, descr().get_animation("idle"));
	state.ivar1 = 1; //  we are available immediately after an idle phase
	return skip_act(); //  wait until signal
}

/**
 * We are released, most likely because the road is no longer busy and we are
 * a second carrier (ox or something). If we promised a flag that we would pick up
 * a ware there, we have to make sure that they do not count on us anymore.
 */
void Carrier::road_pop(Game & game, State & /* state */)
{
	if (m_promised_pickup_to != NOONE && get_location(game)) {
		Road & road      = ref_cast<Road, PlayerImmovable>(*get_location(game));
		Flag & flag      = road.get_flag(static_cast<Road::FlagId>(m_promised_pickup_to));
		Flag & otherflag = road.get_flag(static_cast<Road::FlagId>(m_promised_pickup_to ^ 1));

		flag.cancel_pickup(game, otherflag);
	}
}

/**
 * Fetch an item from a flag, drop it on the other flag.
 * ivar1 is the flag we fetch from, or -1 when we're in the target building.
 *
 * Signal "update" when the road has been split etc.
 */
Bob::Task const Carrier::taskTransport = {
	"transport",
	static_cast<Bob::Ptr>(&Carrier::transport_update),
	0,
	0,
	true
};


/**
 * Begin the transport task.
 */
void Carrier::start_task_transport(Game & game, int32_t const fromflag)
{
	push_task(game, taskTransport);
	top_state().ivar1 = fromflag;
}


void Carrier::transport_update(Game & game, State & state)
{
	std::string signal = get_signal();

	if (signal == "road") {
		signal_handled();
	} else if (signal == "blocked") {
		// Blocked by an ongoing battle
		signal_handled();
		set_animation(game, descr().get_animation("idle"));
		return schedule_act(game, 250);
	} else if (signal.size()) {
		molog("[transport]: Interrupted by signal '%s'\n", signal.c_str());
		return pop_task(game);
	}

	Road & road = ref_cast<Road, PlayerImmovable>(*get_location(game));

	if (state.ivar1 == -1)
		// If we're "in" the target building, special code applies
		deliver_to_building(game, state);

	else if (!does_carry_ware())
		// If we don't carry something, walk to the flag
		pickup_from_flag(game, state);

	else {
		// If the item should go to the building attached to our flag, walk
		// directly into said building
		Flag & flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));

		WareInstance & item = *get_carried_item(game);
		assert(item.get_location(game) == this);

		// A sanity check is necessary, in case the building has been destroyed
		PlayerImmovable * const next = item.get_next_move_step(game);

		if (next && next != &flag && &next->base_flag() == &flag)
			enter_building(game, state);

		// If the flag is overloaded we are allowed to drop items as
		// long as we can pick another up. Otherwise we have to wait.
		else if
			((flag.has_capacity() || !swap_or_wait(game, state))
			 &&
			 !start_task_walktoflag(game, state.ivar1 ^ 1, false))
			// Drop the item, possible exchanging it with another one
			drop_item(game, state);
	}
}


/**
 * Deliver all wares addressed to the building the carrier is already into
 *
 * \param state UNDOCUMENTED
 *
 * \todo Upgrade this function to really support many-wares-at-a-time
 * \todo Document parameter state
 */
void Carrier::deliver_to_building(Game & game, State & state)
{
	BaseImmovable * const pos = game.map()[get_position()].get_immovable();

	if (dynamic_cast<Flag const *>(pos))
		return pop_task(game); //  we are done
	else if (upcast(Building, building, pos)) {
		// Drop all items addressed to this building
		while (WareInstance * const item = get_carried_item(game)) {
			// If the building has disappeared and immediately been replaced
			// with another building, we might have to return without dropping
			// the item.
			PlayerImmovable const * const next = item->get_next_move_step(game);

			if (next == pos) {
				fetch_carried_item(game);
				item->enter_building(game, *building);
			} else {
				molog
					("[Carrier]: Building switch from under us, return to road.\n");

				state.ivar1 =
					&building->base_flag()
					==
					&ref_cast<Road, PlayerImmovable>(*get_location(game)).get_flag
						(static_cast<Road::FlagId>(0));
				break;
			}
		}

		// No more deliverable items. Walk out to the flag.
		return
			start_task_move
				(game,
				 WALK_SE, true,
				 descr().get_right_walk_anims(does_carry_ware()),
				 true);
	} else {
		//  tough luck, the building has disappeared
		molog("[Carrier]: Building disappeared while in building.\n");
		set_location(0);
	}
}


/**
 * Walks to the queued flag and picks up one acked ware
 *
 * \param g Game the carrier lives on
 * \param s Flags sent to the task
 */
void Carrier::pickup_from_flag(Game & game, State & state)
{
	int32_t const ivar1 = state.ivar1;
	if (!start_task_walktoflag(game, ivar1, false)) {

		m_promised_pickup_to = NOONE;

		Road & road      = ref_cast<Road, PlayerImmovable>(*get_location(game));
		Flag & flag      = road.get_flag(static_cast<Road::FlagId>(ivar1));
		Flag & otherflag = road.get_flag(static_cast<Road::FlagId>(ivar1 ^ 1));

		// Are there items to move between our flags?
		if (WareInstance * const item = flag.fetch_pending_item(game, otherflag))
		{
			set_carried_item(game, item);

			set_animation(game, descr().get_animation("idle"));
			return schedule_act(game, 20);
		} else {
			molog("[Carrier]: Nothing suitable on flag.\n");
			return pop_task(game);
		}
	}
}


/**
 * Drop one item in a flag, and pick up a new one if we acked it
 *
 * \param g Game the carrier lives on.
 * \param s Flags sent to the task
 */
void Carrier::drop_item(Game & game, State & state)
{
	WareInstance * other = 0;
	Road & road = ref_cast<Road, PlayerImmovable>(*get_location(game));
	Flag & flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));

	if (m_promised_pickup_to == (state.ivar1 ^ 1)) {
		// If there's an item we acked, we can drop ours even if the flag is
		// flooded
		other =
			flag.fetch_pending_item
				(game, road.get_flag(static_cast<Road::FlagId>(state.ivar1)));

		if (!other && !flag.has_capacity()) {
			molog
				("[Carrier]: strange: acked ware from busy flag no longer "
				 "present.\n");

			m_promised_pickup_to = NOONE;
			set_animation(game, descr().get_animation("idle"));
			return schedule_act(game, 20);
		}

		state.ivar1 = m_promised_pickup_to;
		m_promised_pickup_to = NOONE;
	}

	// Drop our item
	flag.add_item(game, *fetch_carried_item(game));

	// Pick up new load, if any
	if (other) {
		set_carried_item(game, other);

		set_animation(game, descr().get_animation("idle"));
		return schedule_act(game, 20);
	} else
		return pop_task(game);
}


/**
 * When picking up items, if some of them is targeted to the building attached
 * to target flag walk straight into it and deliver.
 *
 * \param g Game the carrier lives on.
 * \param s Flags sent to the task.
 */
void Carrier::enter_building(Game & game, State & state)
{
	if (!start_task_walktoflag(game, state.ivar1 ^ 1, true)) {
		state.ivar1 = -1;
		return
			start_task_move
				(game,
				 WALK_NW, true,
				 descr().get_right_walk_anims(does_carry_ware()),
				 true);
	}
}


/**
 * Swaps items from an overloaded flag for as long as the carrier can pick
 * up new items from it. Otherwise, changes the carrier state to wait.
 *
 * \param g Game the carrier lives on.
 * \param s Flags sent to the task.
 *
 * \return true if the carrier must wait before delivering his wares.
 */
bool Carrier::swap_or_wait(Game & game, State & state)
{
	// Road that employs us
	Road & road = ref_cast<Road, PlayerImmovable>(*get_location(game));
	// Flag we are delivering to
	Flag & flag = road.get_flag(static_cast<Road::FlagId>(state.ivar1 ^ 1));
	// The other flag of our road
	Flag & otherflag = road.get_flag(static_cast<Road::FlagId>(state.ivar1));


	if (m_promised_pickup_to == (state.ivar1 ^ 1)) {
		// All is well, we already acked an item that we can pick up
		// from this flag
		return false;
	} else if (flag.has_pending_item(game, otherflag)) {
		if (!flag.ack_pickup(game, otherflag))
			throw wexception
				("MO(%u): transport: overload exchange: flag %u is fucked up",
				 serial(), flag.serial());

		m_promised_pickup_to = state.ivar1 ^ 1;
		return false;
	} else if (!start_task_walktoflag(game, state.ivar1 ^ 1, false, true))
		start_task_waitforcapacity(game, flag); //  wait one node away

	return true;
}


/**
 * Called by Road code to indicate that a new item has arrived on a flag
 * (0 = start, 1 = end).
 * \return true if the carrier is going to fetch it.
 */
bool Carrier::notify_ware(Game & game, int32_t const flag)
{
	State & state = top_state();

	// Check if we've already acked something
	if (m_promised_pickup_to != NOONE)
		return false;

	// If we are currently in a transport.
	// Explanation:
	//  a) a different carrier / road may be better suited for this ware
	//     (the transport code does not have priorities for the actual
	//     carrier that is notified)
	//  b) the transport task has logic that allows it to
	//     drop an item on an overloaded flag iff it can pick up an item
	//     at the same time.
	//     We should ack said item to avoid more confusion before we move
	//     onto the flag, but we can't do that if we have already acked
	//     something.
	//  c) we might ack for a flag that we are actually moving away from;
	//     this will get us into trouble if items have arrived on the other
	//     flag while we couldn't ack them.
	//
	// (Maybe the need for this lengthy explanation is proof that the
	// ack system needs to be reworked.)
	if (State const * const transport = get_state(taskTransport))
		if
			((transport->ivar1 == -1 && find_closest_flag(game) != flag) ||
			 flag == transport->ivar1)
			return false;

	// Ack it if we haven't
	m_promised_pickup_to = flag;

	if      (state.task == &taskRoad)
		send_signal(game, "ware");
	else if (state.task == &taskWaitforcapacity)
		send_signal(game, "wakeup");

	return true;
}


/**
 * Find a pending item on one of the road's flags, ack it and set m_promised_pickup_to
 * accordingly.
 */
void Carrier::find_pending_item(Game & game)
{
	Road & road = ref_cast<Road, PlayerImmovable>(*get_location(game));
	uint32_t haveitembits = 0;

	assert(m_promised_pickup_to == NOONE);

	if
		(road.get_flag(Road::FlagStart).has_pending_item
		 	(game, road.get_flag(Road::FlagEnd)))
		haveitembits |= 1;

	if
		(road.get_flag(Road::FlagEnd).has_pending_item
		 	(game, road.get_flag(Road::FlagStart)))
		haveitembits |= 2;

	//  If both flags have an item, we pick the one closer to us.
	if (haveitembits == 3)
		haveitembits = 1 << find_closest_flag(game);

	// Ack our decision
	if (haveitembits == 1) {
		m_promised_pickup_to = START_FLAG;
		if
			(!
			 road.get_flag(Road::FlagStart).ack_pickup
			 	(game, road.get_flag(Road::FlagEnd)))
			throw wexception
				("Carrier::find_pending_item: start flag is messed up");

	} else if (haveitembits == 2) {
		m_promised_pickup_to = END_FLAG;
		if
			(!
			 road.get_flag(Road::FlagEnd).ack_pickup
			 	(game, road.get_flag(Road::FlagStart)))
			throw wexception("Carrier::find_pending_item: end flag is messed up");
	}
}


/**
 * Find the flag we are closest to (in walking time).
 */
int32_t Carrier::find_closest_flag(Game & game)
{
	Map & map = game.map();
	CoordPath startpath
		(map, ref_cast<Road, PlayerImmovable>(*get_location(game)).get_path());

	CoordPath endpath;
	int32_t startcost, endcost;
	int32_t curidx = startpath.get_index(get_position());

	// Apparently, we're in a building
	if (curidx < 0) {
		Coords pos = get_position();

		map.get_brn(pos, &pos);

		if (pos == startpath.get_start())
			curidx = 0;
		else if (pos == startpath.get_end())
			curidx = startpath.get_nsteps();
		else
			throw wexception
				("MO(%u): Carrier::find_closest_flag: not on road, not on "
				 "building",
				 serial());
	}

	// Calculate the paths and their associated costs
	endpath = startpath;

	startpath.truncate(curidx);
	startpath.reverse();

	endpath.starttrim(curidx);

	map.calc_cost(startpath, &startcost, 0);
	map.calc_cost(endpath,   &endcost,   0);

	return endcost < startcost;
}


/**
 * Walk to the given flag, or one field before it if offset is true.
 *
 * \return true if a move task has been started, or false if we're already on
 * the target field.
 */
bool Carrier::start_task_walktoflag
	(Game & game, int32_t const flag, bool const ismove2d, bool const offset)
{
	const Path & path =
		ref_cast<Road, PlayerImmovable>(*get_location(game)).get_path();
	int32_t idx;

	if (!flag) {
		idx = 0;
		if (offset)
			++idx;
	} else {
		idx = path.get_nsteps();
		if (offset)
			--idx;
	}

	return
		start_task_movepath
			(game, path, idx, descr().get_right_walk_anims(does_carry_ware()), ismove2d);
}

void Carrier::log_general_info(const Widelands::Editor_Game_Base & egbase)
{
	molog("Carrier at %i,%i\n", get_position().x, get_position().y);

	Worker::log_general_info(egbase);

	molog("m_promised_pickup_to = %i\n", m_promised_pickup_to);
}

/*
==============================

Load/save support

==============================
*/

#define CARRIER_SAVEGAME_VERSION 1

Carrier::Loader::Loader()
{
}

void Carrier::Loader::load(FileRead & fr)
{
	Worker::Loader::load(fr);

	uint8_t version = fr.Unsigned8();
	if (version != CARRIER_SAVEGAME_VERSION)
		throw game_data_error("unknown/unhandled version %u", version);

	Carrier & carrier = get<Carrier>();
	carrier.m_promised_pickup_to = fr.Signed32();
}

const Bob::Task * Carrier::Loader::get_task(const std::string & name)
{
	if (name == "road") return &taskRoad;
	if (name == "transport") return &taskTransport;
	return Worker::Loader::get_task(name);
}

Carrier::Loader * Carrier::create_loader()
{
	return new Loader;
}

void Carrier::do_save
	(Editor_Game_Base & egbase, Map_Map_Object_Saver & mos, FileWrite & fw)
{
	Worker::do_save(egbase, mos, fw);

	fw.Unsigned8(CARRIER_SAVEGAME_VERSION);
	fw.Signed32(m_promised_pickup_to);
}

}