1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "WaitCommandsAI.h"
5 #include "SelectedUnitsHandler.h"
6 #include "GameHelper.h"
7 #include "GlobalUnsynced.h"
8 #include "UI/CommandColors.h"
9 #include "UI/CursorIcons.h"
10 #include "Rendering/LineDrawer.h"
11 #include "Sim/Misc/QuadField.h"
12 #include "Sim/Misc/GlobalConstants.h"
13 #include "Sim/Misc/GlobalSynced.h"
14 #include "Sim/Misc/TeamHandler.h"
15 #include "Sim/Units/Unit.h"
16 #include "Sim/Units/UnitHandler.h"
17 #include "Sim/Units/CommandAI/CommandQueue.h"
18 #include "Sim/Units/CommandAI/CommandAI.h"
19 #include "Sim/Units/CommandAI/FactoryCAI.h"
20 #include "Sim/Units/UnitTypes/Factory.h"
21 #include "System/Object.h"
22 #include "System/Util.h"
23 #include "System/creg/STL_Map.h"
24 #include "System/creg/STL_List.h"
25 #include "System/creg/STL_Set.h"
26 #include <assert.h>
27 
28 CWaitCommandsAI waitCommandsAI;
29 
30 
31 static const int maxNetDelay = 30;  // in seconds
32 
33 static const int updatePeriod = 3;  // in GAME_SPEED, 100 ms
34 
35 
36 CR_BIND(CWaitCommandsAI, )
37 CR_REG_METADATA(CWaitCommandsAI, (
38 	CR_MEMBER(waitMap),
39 	CR_MEMBER(unackedMap)
40 ))
41 
CR_BIND_DERIVED_INTERFACE(CWaitCommandsAI::Wait,CObject)42 CR_BIND_DERIVED_INTERFACE(CWaitCommandsAI::Wait, CObject)
43 CR_REG_METADATA_SUB(CWaitCommandsAI,Wait, (
44 	CR_MEMBER(code),
45 	CR_MEMBER(key),
46 	CR_MEMBER(valid),
47 	CR_MEMBER(deadTime),
48 	CR_POSTLOAD(PostLoad)
49 ))
50 
51 CR_BIND_DERIVED(CWaitCommandsAI::TimeWait, CWaitCommandsAI::Wait, (1,0))
52 CR_REG_METADATA_SUB(CWaitCommandsAI,TimeWait , (
53 	CR_MEMBER(unit),
54 	CR_MEMBER(enabled),
55 	CR_MEMBER(duration),
56 	CR_MEMBER(endFrame),
57 	CR_MEMBER(factory)
58 ))
59 
60 CR_BIND_DERIVED(CWaitCommandsAI::DeathWait, CWaitCommandsAI::Wait, (Command()))
61 CR_REG_METADATA_SUB(CWaitCommandsAI,DeathWait , (
62 	CR_MEMBER(waitUnits),
63 	CR_MEMBER(deathUnits),
64 	CR_MEMBER(unitPos)
65 ))
66 
67 CR_BIND_DERIVED(CWaitCommandsAI::SquadWait, CWaitCommandsAI::Wait, (Command()))
68 CR_REG_METADATA_SUB(CWaitCommandsAI,SquadWait , (
69 	CR_MEMBER(squadCount),
70 	CR_MEMBER(buildUnits),
71 	CR_MEMBER(waitUnits),
72 	CR_MEMBER(stateText)
73 ))
74 
75 CR_BIND_DERIVED(CWaitCommandsAI::GatherWait, CWaitCommandsAI::Wait, (Command()))
76 CR_REG_METADATA_SUB(CWaitCommandsAI,GatherWait , (
77 	CR_MEMBER(waitUnits)
78 ))
79 
80 /******************************************************************************/
81 /******************************************************************************/
82 
83 CWaitCommandsAI::CWaitCommandsAI()
84 {
85 	assert(sizeof(float) == sizeof(KeyType));
86 }
87 
88 
~CWaitCommandsAI()89 CWaitCommandsAI::~CWaitCommandsAI()
90 {
91 	WaitMap::iterator it;
92 	for (it = waitMap.begin(); it != waitMap.end(); ++it) {
93 		delete it->second;
94 	}
95 	for (it = unackedMap.begin(); it != unackedMap.end(); ++it) {
96 		delete it->second;
97 	}
98 }
99 
100 
Update()101 void CWaitCommandsAI::Update()
102 {
103 //	if ((gs->frameNum % GAME_SPEED) == 0) printf("Waits: %i\n", waitMap.size()); // FIXME
104 
105 	// limit the updates
106 	if ((gs->frameNum % updatePeriod) != 0) {
107 		return;
108 	}
109 
110 	// update the waits
111 	WaitMap::iterator it;
112 	it = waitMap.begin();
113 	while (it != waitMap.end()) {
114 		// grab an incremented iterator in case the active iterator deletes itself
115 		WaitMap::iterator tmp = it;
116 		++tmp;
117 		it->second->Update();
118 		it = tmp;
119 	}
120 
121 	// delete old unacknowledged waits
122 	const spring_time nowTime = spring_gettime();
123 	it = unackedMap.begin();
124 	while (it != unackedMap.end()) {
125 		WaitMap::iterator tmp = it;
126 		++tmp;
127 		Wait* wait = it->second;
128 		if (wait->GetDeadTime() < nowTime) {
129 			delete wait;
130 			unackedMap.erase(it);
131 		}
132 		it = tmp;
133 	}
134 }
135 
136 
DrawCommands() const137 void CWaitCommandsAI::DrawCommands() const
138 {
139 	WaitMap::const_iterator it;
140 	for (it = waitMap.begin(); it != waitMap.end(); ++it) {
141 		it->second->Draw();
142 	}
143 }
144 
145 
AddTimeWait(const Command & cmd)146 void CWaitCommandsAI::AddTimeWait(const Command& cmd)
147 {
148 	// save the current selection
149 	const CUnitSet tmpSet = selectedUnitsHandler.selectedUnits;
150 	CUnitSet::const_iterator it;
151 	for (it = tmpSet.begin(); it != tmpSet.end(); ++it) {
152 		InsertWaitObject(TimeWait::New(cmd, *it));
153 	}
154 	// restore the selection
155 	selectedUnitsHandler.ClearSelected();
156 	for (it = tmpSet.begin(); it != tmpSet.end(); ++it) {
157 		selectedUnitsHandler.AddUnit(*it);
158 	}
159 }
160 
161 
AddDeathWait(const Command & cmd)162 void CWaitCommandsAI::AddDeathWait(const Command& cmd)
163 {
164 	InsertWaitObject(DeathWait::New(cmd));
165 }
166 
167 
AddSquadWait(const Command & cmd)168 void CWaitCommandsAI::AddSquadWait(const Command& cmd)
169 {
170 	InsertWaitObject(SquadWait::New(cmd));
171 }
172 
173 
AddGatherWait(const Command & cmd)174 void CWaitCommandsAI::AddGatherWait(const Command& cmd)
175 {
176 	InsertWaitObject(GatherWait::New(cmd));
177 }
178 
179 
AcknowledgeCommand(const Command & cmd)180 void CWaitCommandsAI::AcknowledgeCommand(const Command& cmd)
181 {
182 	if ((cmd.GetID() != CMD_WAIT) || (cmd.params.size() != 2)) {
183 		return;
184 	}
185 	const KeyType key = Wait::GetKeyFromFloat(cmd.params[1]);
186 	WaitMap::iterator it = unackedMap.find(key);
187 	if (it != unackedMap.end()) {
188 		Wait* wait = it->second;
189 		if (wait->GetCode() != cmd.params[0]) {
190 			return; // code mismatch
191 		}
192 		// move into the acknowledged pool
193 		unackedMap.erase(key);
194 		waitMap[key] = wait;
195 	}
196 }
197 
198 
AddLocalUnit(CUnit * unit,const CUnit * builder)199 void CWaitCommandsAI::AddLocalUnit(CUnit* unit, const CUnit* builder)
200 {
201 	// NOTE: the wait keys will link the right units to
202 	//       the correct player (for multi-player teams)
203 	if ((unit->team != gu->myTeam) || waitMap.empty()) {
204 		return;
205 	}
206 
207 	const CCommandQueue& dq = unit->commandAI->commandQue;
208 	CCommandQueue::const_iterator qit;
209 	for (qit = dq.begin(); qit != dq.end(); ++qit) {
210 		const Command& cmd = *qit;
211 		if ((cmd.GetID() != CMD_WAIT) || (cmd.params.size() != 2)) {
212 			continue;
213 		}
214 
215 		const KeyType key = Wait::GetKeyFromFloat(cmd.params[1]);
216 		WaitMap::iterator wit = waitMap.find(key);
217 		if (wit == waitMap.end()) {
218 			continue;
219 		}
220 
221 		Wait* wait = wit->second;
222 		if (cmd.params[0] != wait->GetCode()) {
223 			continue;
224 		}
225 
226 		const float code = cmd.params[0];
227 		if (code != CMD_WAITCODE_TIMEWAIT) {
228 			wait->AddUnit(unit);
229 		}
230 		else {
231 			// add a unit-specific TimeWait
232 			// (straight into the waitMap, no net ack required)
233 			const int duration = static_cast<TimeWait*>(wait)->GetDuration();
234 			TimeWait* tw = TimeWait::New(duration, unit);
235 			if (tw != NULL) {
236 				if (waitMap.find(tw->GetKey()) != waitMap.end()) {
237 					delete tw;
238 				} else {
239 					waitMap[tw->GetKey()] = tw;
240 					// should not affect the sync state
241 					const_cast<Command&>(cmd).params[1] =
242 						Wait::GetFloatFromKey(tw->GetKey());
243 				}
244 			}
245 		}
246 	}
247 }
248 
249 
RemoveWaitCommand(CUnit * unit,const Command & cmd)250 void CWaitCommandsAI::RemoveWaitCommand(CUnit* unit, const Command& cmd)
251 {
252 	if ((cmd.params.size() != 2) ||
253 	    (unit->team != gu->myTeam)) {
254 		return;
255 	}
256 	const KeyType key = Wait::GetKeyFromFloat(cmd.params[1]);
257 	WaitMap::iterator it = waitMap.find(key);
258 	if (it != waitMap.end()) {
259 		it->second->RemoveUnit(unit);
260 	}
261 }
262 
263 
ClearUnitQueue(CUnit * unit,const CCommandQueue & queue)264 void CWaitCommandsAI::ClearUnitQueue(CUnit* unit, const CCommandQueue& queue)
265 {
266 	if ((unit->team != gu->myTeam) || waitMap.empty()) {
267 		return;
268 	}
269 	CCommandQueue::const_iterator qit;
270 	for (qit = queue.begin(); qit != queue.end(); ++qit) {
271 		const Command& cmd = *qit;
272 		if ((cmd.GetID() == CMD_WAIT) && (cmd.params.size() == 2)) {
273 			const KeyType key = Wait::GetKeyFromFloat(cmd.params[1]);
274 			WaitMap::iterator wit = waitMap.find(key);
275 			if (wit != waitMap.end()) {
276 				wit->second->RemoveUnit(unit);
277 			}
278 		}
279 	}
280 }
281 
282 
InsertWaitObject(Wait * wait)283 bool CWaitCommandsAI::InsertWaitObject(Wait* wait)
284 {
285 	if (wait == NULL) {
286 		return false;
287 	}
288 	if (unackedMap.find(wait->GetKey()) != unackedMap.end()) {
289 		return false;
290 	}
291 	unackedMap[wait->GetKey()] = wait;
292 	return true;
293 }
294 
295 
RemoveWaitObject(Wait * wait)296 void CWaitCommandsAI::RemoveWaitObject(Wait* wait)
297 {
298 	if (waitMap.erase(wait->GetKey()))
299 		 return;
300 	if (unackedMap.erase(wait->GetKey()))
301 		 return;
302 }
303 
304 
AddIcon(const Command & cmd,const float3 & pos) const305 void CWaitCommandsAI::AddIcon(const Command& cmd, const float3& pos) const
306 {
307 	if (cmd.params.size() != 2) {
308 		lineDrawer.DrawIconAtLastPos(CMD_WAIT);
309 		return;
310 	}
311 
312 	const float code = cmd.params[0];
313 	const KeyType key = Wait::GetKeyFromFloat(cmd.params[1]);
314 	WaitMap::const_iterator it = waitMap.find(key);
315 
316 	if (it == waitMap.end()) {
317 		lineDrawer.DrawIconAtLastPos(CMD_WAIT);
318 	}
319 	else if (code == CMD_WAITCODE_TIMEWAIT) {
320 		lineDrawer.DrawIconAtLastPos(CMD_TIMEWAIT);
321 		cursorIcons.AddIconText(it->second->GetStateText(), pos);
322 	}
323 	else if (code == CMD_WAITCODE_SQUADWAIT) {
324 		lineDrawer.DrawIconAtLastPos(CMD_SQUADWAIT);
325 		cursorIcons.AddIconText(it->second->GetStateText(), pos);
326 	}
327 	else if (code == CMD_WAITCODE_DEATHWAIT) {
328 		lineDrawer.DrawIconAtLastPos(CMD_DEATHWAIT);
329 		it->second->AddUnitPosition(pos);
330 	}
331 	else if (code == CMD_WAITCODE_GATHERWAIT) {
332 		lineDrawer.DrawIconAtLastPos(CMD_GATHERWAIT);
333 	}
334 	else {
335 		lineDrawer.DrawIconAtLastPos(CMD_WAIT);
336 	}
337 }
338 
339 
340 /******************************************************************************/
341 //
342 //  Wait Base Class
343 //
344 
345 CWaitCommandsAI::KeyType CWaitCommandsAI::Wait::keySource = 0;
346 
347 const string CWaitCommandsAI::Wait::noText = "";
348 
349 
350 // static
GetNewKey()351 CWaitCommandsAI::KeyType CWaitCommandsAI::Wait::GetNewKey()
352 {
353 	keySource = (0xffffff00 & keySource) |
354 	            (0x000000ff & gu->myPlayerNum);
355 	keySource += 0x00000100;
356 	return keySource;
357 }
358 
359 
360 // static
GetKeyFromFloat(float f)361 CWaitCommandsAI::KeyType CWaitCommandsAI::Wait::GetKeyFromFloat(float f)
362 {
363 	return *((KeyType*)&f);
364 }
365 
366 
PostLoad()367 void CWaitCommandsAI::Wait::PostLoad()
368 {
369 	deadTime = spring_gettime() + spring_secs(maxNetDelay);
370 }
371 
372 // static
GetFloatFromKey(KeyType k)373 float CWaitCommandsAI::Wait::GetFloatFromKey(KeyType k)
374 {
375 	return *((float*)&k);
376 }
377 
378 
Wait(float _code)379 CWaitCommandsAI::Wait::Wait(float _code)
380 	: code(_code),
381 	key(0),
382 	valid(false),
383 	deadTime(spring_gettime() + spring_secs(maxNetDelay))
384 {
385 }
386 
387 
~Wait()388 CWaitCommandsAI::Wait::~Wait()
389 {
390 	waitCommandsAI.RemoveWaitObject(this);
391 }
392 
393 
394 CWaitCommandsAI::Wait::WaitState
GetWaitState(const CUnit * unit) const395 	CWaitCommandsAI::Wait::GetWaitState(const CUnit* unit) const
396 {
397 	const CCommandQueue& dq = unit->commandAI->commandQue;
398 	if (dq.empty()) {
399 		return Missing;
400 	}
401 	const Command& cmd = dq.front();
402 	if ((cmd.GetID() == CMD_WAIT) && (cmd.params.size() == 2) &&
403 			(cmd.params[0] == code) &&
404 			(GetKeyFromFloat(cmd.params[1]) == key)) {
405 		return Active;
406 	}
407 
408 	CCommandQueue::const_iterator it = dq.begin();
409 	++it;
410 	for ( ; it != dq.end(); ++it) {
411 		const Command& qcmd = *it;
412 		if ((qcmd.GetID() == CMD_WAIT) && (qcmd.params.size() == 2) &&
413 				(qcmd.params[0] == code) &&
414 				(GetKeyFromFloat(qcmd.params[1]) == key)) {
415 			return Queued;
416 		}
417 	}
418 	return Missing;
419 }
420 
421 
IsWaitingOn(const CUnit * unit) const422 bool CWaitCommandsAI::Wait::IsWaitingOn(const CUnit* unit) const
423 {
424 	const CCommandQueue& dq = unit->commandAI->commandQue;
425 	if (dq.empty()) {
426 		return false;
427 	}
428 	const Command& cmd = dq.front();
429 	if ((cmd.GetID() == CMD_WAIT) && (cmd.params.size() == 2) &&
430 			(cmd.params[0] == code) &&
431 			(GetKeyFromFloat(cmd.params[1]) == key)) {
432 		return true;
433 	}
434 	return false;
435 }
436 
437 
SendCommand(const Command & cmd,const CUnitSet & unitSet)438 void CWaitCommandsAI::Wait::SendCommand(const Command& cmd,
439 																				const CUnitSet& unitSet)
440 {
441 	if (unitSet.empty()) {
442 		return;
443 	}
444 
445 	const CUnitSet& selUnits = selectedUnitsHandler.selectedUnits;
446 	if (unitSet == selUnits) {
447 		selectedUnitsHandler.GiveCommand(cmd, false);
448 		return;
449 	}
450 
451 	CUnitSet tmpSet = selUnits;
452 	CUnitSet::const_iterator it;
453 
454 	selectedUnitsHandler.ClearSelected();
455 	for (it = unitSet.begin(); it != unitSet.end(); ++it) {
456 		selectedUnitsHandler.AddUnit(*it);
457 	}
458 
459 	selectedUnitsHandler.GiveCommand(cmd, false);
460 
461 	selectedUnitsHandler.ClearSelected();
462 	for (it = tmpSet.begin(); it != tmpSet.end(); ++it) {
463 		selectedUnitsHandler.AddUnit(*it);
464 	}
465 }
466 
467 
SendWaitCommand(const CUnitSet & unitSet)468 void CWaitCommandsAI::Wait::SendWaitCommand(const CUnitSet& unitSet)
469 {
470 	Command waitCmd(CMD_WAIT);
471 	SendCommand(waitCmd, unitSet);
472 }
473 
474 
RemoveUnitFromSet(CUnitSet::iterator it,CUnitSet & unitSet)475 CUnitSet::iterator CWaitCommandsAI::Wait::RemoveUnitFromSet(CUnitSet::iterator it, CUnitSet& unitSet)
476 {
477 	CUnitSet::iterator tmp = it;
478 	++tmp;
479 	unitSet.erase(it);
480 	return tmp;
481 }
482 
483 
484 /******************************************************************************/
485 //
486 //  TimeWait
487 //
488 
489 CWaitCommandsAI::TimeWait*
New(const Command & cmd,CUnit * unit)490 	CWaitCommandsAI::TimeWait::New(const Command& cmd, CUnit* unit)
491 {
492 	TimeWait* tw = new TimeWait(cmd, unit);
493 	if (!tw->valid) {
494 		delete tw;
495 		return NULL;
496 	}
497 	return tw;
498 }
499 
500 
501 CWaitCommandsAI::TimeWait*
New(int duration,CUnit * unit)502 	CWaitCommandsAI::TimeWait::New(int duration, CUnit* unit)
503 {
504 	TimeWait* tw = new TimeWait(duration, unit);
505 	if (!tw->valid) {
506 		delete tw;
507 		return NULL;
508 	}
509 	return tw;
510 }
511 
512 
TimeWait(const Command & cmd,CUnit * _unit)513 CWaitCommandsAI::TimeWait::TimeWait(const Command& cmd, CUnit* _unit)
514 : Wait(CMD_WAITCODE_TIMEWAIT)
515 {
516 	if (cmd.params.size() != 1) {
517 		return;
518 	}
519 
520 	valid = true;
521 	key = GetNewKey();
522 
523 	unit = _unit;
524 	enabled = false;
525 	endFrame = 0;
526 	duration = GAME_SPEED * (int)cmd.params[0];
527 	factory = (dynamic_cast<CFactory*>(unit) != NULL);
528 
529 	Command waitCmd(CMD_WAIT, cmd.options, code);
530 	waitCmd.PushParam(GetFloatFromKey(key));
531 
532 	selectedUnitsHandler.ClearSelected();
533 	selectedUnitsHandler.AddUnit(unit);
534 	selectedUnitsHandler.GiveCommand(waitCmd);
535 
536 	AddDeathDependence(unit, DEPENDENCE_WAITCMD);
537 
538 	return;
539 }
540 
541 
TimeWait(int _duration,CUnit * _unit)542 CWaitCommandsAI::TimeWait::TimeWait(int _duration, CUnit* _unit)
543 : Wait(CMD_WAITCODE_TIMEWAIT)
544 {
545 	valid = true;
546 	key = GetNewKey();
547 
548 	unit = _unit;
549 	enabled = false;
550 	endFrame = 0;
551 	duration = _duration;
552 	factory = false;
553 
554 	AddDeathDependence(unit, DEPENDENCE_WAITCMD);
555 }
556 
557 
~TimeWait()558 CWaitCommandsAI::TimeWait::~TimeWait()
559 {
560 	// do nothing
561 }
562 
563 
DependentDied(CObject * object)564 void CWaitCommandsAI::TimeWait::DependentDied(CObject* object)
565 {
566 	unit = NULL;
567 }
568 
569 
AddUnit(CUnit * unit)570 void CWaitCommandsAI::TimeWait::AddUnit(CUnit* unit)
571 {
572 	// do nothing
573 }
574 
575 
RemoveUnit(CUnit * _unit)576 void CWaitCommandsAI::TimeWait::RemoveUnit(CUnit* _unit)
577 {
578 	if (_unit == unit) {
579 		delete this;
580 		return;
581 	}
582 }
583 
584 
Update()585 void CWaitCommandsAI::TimeWait::Update()
586 {
587 	if (unit == NULL) {
588 		delete this;
589 		return;
590 	}
591 
592 	WaitState state = GetWaitState(unit);
593 
594 	if (state == Active) {
595 		if (!enabled) {
596 			enabled = true;
597 			endFrame = (gs->frameNum + duration);
598 		}
599 		else {
600 			if (endFrame <= gs->frameNum) {
601 				CUnitSet smallSet;
602 				smallSet.insert(unit);
603 				SendWaitCommand(smallSet);
604 				if (!factory) {
605 					delete this;
606 					return;
607 				} else {
608 					enabled = false;
609 				}
610 			}
611 		}
612 	}
613 	else if (state == Queued) {
614 		return;
615 	}
616 	else if (state == Missing) {
617 		if (!factory) { // FIXME
618 			delete this;
619 			return;
620 		}
621 	}
622 }
623 
624 
GetStateText() const625 const string& CWaitCommandsAI::TimeWait::GetStateText() const
626 {
627 	static char buf[32];
628 	if (enabled) {
629 		const int remaining =
630 			1 + (std::max(0, (endFrame - gs->frameNum - 1)) / GAME_SPEED);
631 		SNPRINTF(buf, sizeof(buf), "%i", remaining);
632 	} else {
633 		SNPRINTF(buf, sizeof(buf), "%i", duration / GAME_SPEED);
634 	}
635 	static string text;
636 	text = buf;
637 	return text;
638 }
639 
640 
Draw() const641 void CWaitCommandsAI::TimeWait::Draw() const
642 {
643 	// do nothing
644 }
645 
646 
647 /******************************************************************************/
648 //
649 //  DeathWait
650 //
651 
652 CWaitCommandsAI::DeathWait*
New(const Command & cmd)653 	CWaitCommandsAI::DeathWait::New(const Command& cmd)
654 {
655 	DeathWait* dw = new DeathWait(cmd);
656 	if (!dw->valid) {
657 		delete dw;
658 		return NULL;
659 	}
660 	return dw;
661 }
662 
663 
DeathWait(const Command & cmd)664 CWaitCommandsAI::DeathWait::DeathWait(const Command& cmd)
665 : Wait(CMD_WAITCODE_DEATHWAIT)
666 {
667 	const CUnitSet& selUnits = selectedUnitsHandler.selectedUnits;
668 
669 	if (cmd.params.size() == 1) {
670 		const int unitID = (int)cmd.params[0];
671 		if ((unitID < 0) || (static_cast<size_t>(unitID) >= unitHandler->MaxUnits())) {
672 			return;
673 		}
674 		CUnit* unit = unitHandler->units[unitID];
675 		if (unit == NULL) {
676 			return;
677 		}
678 		if (selUnits.find(unit) != selUnits.end()) {
679 			return;
680 		}
681 		deathUnits.insert(unit);
682 	}
683 	else if (cmd.params.size() == 6) {
684 		const float3& pos0 = cmd.GetPos(0);
685 		const float3& pos1 = cmd.GetPos(3);
686 		CUnitSet tmpSet;
687 		SelectAreaUnits(pos0, pos1, tmpSet, false);
688 		CUnitSet::iterator it;
689 		for (it = tmpSet.begin(); it != tmpSet.end(); ++it) {
690 			if (selUnits.find(*it) == selUnits.end()) {
691 				deathUnits.insert(*it);
692 			}
693 		}
694 		if (deathUnits.empty()) {
695 			return;
696 		}
697 	}
698 	else {
699 		return; // unknown param config
700 	}
701 
702 	valid = true;
703 	key = GetNewKey();
704 
705 	waitUnits = selUnits;
706 
707 	Command waitCmd(CMD_WAIT, cmd.options, code);
708 	waitCmd.PushParam(GetFloatFromKey(key));
709 	selectedUnitsHandler.GiveCommand(waitCmd);
710 
711 	CUnitSet::iterator it;
712 	for (it = waitUnits.begin(); it != waitUnits.end(); ++it) {
713 		AddDeathDependence((CObject*)(*it), DEPENDENCE_WAITCMD);
714 	}
715 	for (it = deathUnits.begin(); it != deathUnits.end(); ++it) {
716 		AddDeathDependence((CObject*)(*it), DEPENDENCE_WAITCMD);
717 	}
718 
719 	return;
720 }
721 
722 
~DeathWait()723 CWaitCommandsAI::DeathWait::~DeathWait()
724 {
725 	// do nothing
726 }
727 
728 
DependentDied(CObject * object)729 void CWaitCommandsAI::DeathWait::DependentDied(CObject* object)
730 {
731 	waitUnits.erase(static_cast<CUnit*>(object));
732 
733 	if (waitUnits.empty())
734 		return;
735 
736 	deathUnits.erase(static_cast<CUnit*>(object));
737 }
738 
739 
AddUnit(CUnit * unit)740 void CWaitCommandsAI::DeathWait::AddUnit(CUnit* unit)
741 {
742 	if (waitUnits.insert(unit).second)
743 		AddDeathDependence(unit, DEPENDENCE_WAITCMD);
744 }
745 
746 
RemoveUnit(CUnit * unit)747 void CWaitCommandsAI::DeathWait::RemoveUnit(CUnit* unit)
748 {
749 	if (waitUnits.erase(unit))
750 		DeleteDeathDependence(unit, DEPENDENCE_WAITCMD);
751 }
752 
753 
Update()754 void CWaitCommandsAI::DeathWait::Update()
755 {
756 	if (waitUnits.empty()) {
757 		delete this;
758 		return;
759 	}
760 
761 	unitPos.clear();
762 
763 	if (!deathUnits.empty()) {
764 		return; // more must die
765 	}
766 
767 	CUnitSet unblockSet;
768 	CUnitSet::iterator it = waitUnits.begin();
769 	while (it != waitUnits.end()) {
770 		WaitState state = GetWaitState(*it);
771 		if (state == Active) {
772 			unblockSet.insert(*it);
773 			DeleteDeathDependence(*it, DEPENDENCE_WAITCMD);
774 			it = RemoveUnitFromSet(it, waitUnits);
775 			continue;
776 		}
777 		else if (state == Queued) {
778 			// do nothing
779 		}
780 		else if (state == Missing) {
781 			DeleteDeathDependence(*it, DEPENDENCE_WAITCMD);
782 			it = RemoveUnitFromSet(it, waitUnits);
783 			continue;
784 		}
785 		++it;
786 	}
787 	SendWaitCommand(unblockSet);
788 	if (waitUnits.empty()) {
789 		delete this;
790 		return;
791 	}
792 }
793 
794 
Draw() const795 void CWaitCommandsAI::DeathWait::Draw() const
796 {
797 	if (unitPos.empty()) {
798 		return;
799 	}
800 
801 	float3 midPos(0.0f, 0.0f, 0.0f);
802 	for (size_t i = 0; i < unitPos.size(); i++) {
803 		midPos += unitPos.at(i);
804 	}
805 	midPos /= (float)unitPos.size();
806 
807 	cursorIcons.AddIcon(CMD_DEATHWAIT, midPos);
808 
809 	for (size_t i = 0; i < unitPos.size(); i++) {
810 		lineDrawer.StartPath(unitPos.at(i), cmdColors.start);
811 		lineDrawer.DrawLine(midPos, cmdColors.deathWait);
812 		lineDrawer.FinishPath();
813 	}
814 
815 	CUnitSet::const_iterator it;
816 	for (it = deathUnits.begin(); it != deathUnits.end(); ++it) {
817 		const CUnit* unit = *it;
818 		if (unit->losStatus[gu->myAllyTeam] & (LOS_INLOS | LOS_INRADAR)) {
819 			cursorIcons.AddIcon(CMD_SELFD, unit->midPos);
820 			lineDrawer.StartPath(midPos, cmdColors.start);
821 			lineDrawer.DrawLine(unit->midPos, cmdColors.deathWait);
822 			lineDrawer.FinishPath();
823 		}
824 	}
825 }
826 
827 
AddUnitPosition(const float3 & pos)828 void CWaitCommandsAI::DeathWait::AddUnitPosition(const float3& pos)
829 {
830 	unitPos.push_back(pos);
831 }
832 
833 
SelectAreaUnits(const float3 & pos0,const float3 & pos1,CUnitSet & units,bool enemies)834 void CWaitCommandsAI::DeathWait::SelectAreaUnits(
835 	const float3& pos0, const float3& pos1, CUnitSet& units, bool enemies)
836 {
837 	units.clear();
838 
839 	const float3 mins(std::min(pos0.x, pos1.x), 0.0f, std::min(pos0.z, pos1.z));
840 	const float3 maxs(std::max(pos0.x, pos1.x), 0.0f, std::max(pos0.z, pos1.z));
841 
842 	const std::vector<CUnit*> &tmpUnits = quadField->GetUnitsExact(mins, maxs);
843 
844 	const int count = (int)tmpUnits.size();
845 	for (int i = 0; i < count; i++) {
846 		CUnit* unit = tmpUnits[i];
847 		if (enemies && teamHandler->Ally(unit->allyteam, gu->myAllyTeam)) {
848 			continue;
849 		}
850 		if (!(unit->losStatus[gu->myAllyTeam] & (LOS_INLOS | LOS_INRADAR))) {
851 			continue;
852 		}
853 		units.insert(unit);
854 	}
855 }
856 
857 
858 /******************************************************************************/
859 //
860 //  SquadWait
861 //
862 
863 CWaitCommandsAI::SquadWait*
New(const Command & cmd)864 	CWaitCommandsAI::SquadWait::New(const Command& cmd)
865 {
866 	SquadWait* sw = new SquadWait(cmd);
867 	if (!sw->valid) {
868 		delete sw;
869 		return NULL;
870 	}
871 	return sw;
872 }
873 
874 
SquadWait(const Command & cmd)875 CWaitCommandsAI::SquadWait::SquadWait(const Command& cmd)
876 : Wait(CMD_WAITCODE_SQUADWAIT)
877 {
878 	if (cmd.params.size() != 1) {
879 		return;
880 	}
881 
882 	squadCount = (int)cmd.params[0];
883 	if (squadCount < 2) {
884 		return;
885 	}
886 
887 	const CUnitSet& selUnits = selectedUnitsHandler.selectedUnits;
888 	CUnitSet::const_iterator it;
889 	for (it = selUnits.begin(); it != selUnits.end(); ++it) {
890 		CUnit* unit = *it;
891 		if (dynamic_cast<CFactory*>(unit)) {
892 			buildUnits.insert(unit);
893 		} else {
894 			waitUnits.insert(unit);
895 		}
896 	}
897 	if (buildUnits.empty() && ((int)waitUnits.size() < squadCount)) {
898 		return;
899 	}
900 
901 	valid = true;
902 	key = GetNewKey();
903 
904 	Command waitCmd(CMD_WAIT, cmd.options, code);
905 	waitCmd.PushParam(GetFloatFromKey(key));
906 
907 	SendCommand(waitCmd, buildUnits);
908 	SendCommand(waitCmd, waitUnits);
909 
910 	for (it = buildUnits.begin(); it != buildUnits.end(); ++it) {
911 		AddDeathDependence((CObject*)(*it), DEPENDENCE_WAITCMD);
912 	}
913 	for (it = waitUnits.begin(); it != waitUnits.end(); ++it) {
914 		AddDeathDependence((CObject*)(*it), DEPENDENCE_WAITCMD);
915 	}
916 
917 	UpdateText();
918 
919 	return;
920 }
921 
922 
~SquadWait()923 CWaitCommandsAI::SquadWait::~SquadWait()
924 {
925 	// do nothing
926 }
927 
928 
DependentDied(CObject * object)929 void CWaitCommandsAI::SquadWait::DependentDied(CObject* object)
930 {
931 	buildUnits.erase(static_cast<CUnit*>(object));
932 	waitUnits.erase(static_cast<CUnit*>(object));
933 }
934 
935 
AddUnit(CUnit * unit)936 void CWaitCommandsAI::SquadWait::AddUnit(CUnit* unit)
937 {
938 	if (waitUnits.insert(unit).second)
939 		AddDeathDependence(unit, DEPENDENCE_WAITCMD);
940 }
941 
942 
RemoveUnit(CUnit * unit)943 void CWaitCommandsAI::SquadWait::RemoveUnit(CUnit* unit)
944 {
945 	if (buildUnits.erase(unit))
946 		DeleteDeathDependence(unit, DEPENDENCE_WAITCMD);
947 	if (waitUnits.erase(unit))
948 		DeleteDeathDependence(unit, DEPENDENCE_WAITCMD);
949 }
950 
951 
Update()952 void CWaitCommandsAI::SquadWait::Update()
953 {
954 	if (buildUnits.empty() && ((int)waitUnits.size() < squadCount)) {
955 		// FIXME -- unblock remaining waitUnits ?
956 		delete this;
957 		return;
958 	}
959 
960 	if ((int)waitUnits.size() >= squadCount) {
961 		CUnitSet unblockSet;
962 		CUnitSet::iterator it = waitUnits.begin();
963 		while (it != waitUnits.end()) {
964 			WaitState state = GetWaitState(*it);
965 			if (state == Active) {
966 				unblockSet.insert(*it);
967 				if ((int)unblockSet.size() >= squadCount) {
968 					break; // we've got our squad
969 				}
970 			}
971 			else if (state == Queued) {
972 				// do nothing
973 			}
974 			else if (state == Missing) {
975 				DeleteDeathDependence(*it, DEPENDENCE_WAITCMD);
976 				it = RemoveUnitFromSet(it, waitUnits);
977 				continue;
978 			}
979 			++it;
980 		}
981 
982 		if ((int)unblockSet.size() >= squadCount) {
983 			// FIXME -- rebuild the order queue so
984 			//          that formations are created?
985 			SendWaitCommand(unblockSet);
986 			for (it = unblockSet.begin(); it != unblockSet.end(); ++it) {
987 				if (waitUnits.erase(*it))
988 					DeleteDeathDependence(*it, DEPENDENCE_WAITCMD);
989 			}
990 		}
991 	}
992 
993 	UpdateText();
994 	// FIXME -- clean builders
995 }
996 
997 
UpdateText()998 void CWaitCommandsAI::SquadWait::UpdateText()
999 {
1000 	static char buf[64];
1001 	SNPRINTF(buf, sizeof(buf), "%i/%i", (int)waitUnits.size(), squadCount);
1002 	stateText = buf;
1003 }
1004 
1005 
Draw() const1006 void CWaitCommandsAI::SquadWait::Draw() const
1007 {
1008 	// do nothing
1009 }
1010 
1011 
1012 /******************************************************************************/
1013 //
1014 //  GatherWait
1015 //
1016 
1017 CWaitCommandsAI::GatherWait*
New(const Command & cmd)1018 	CWaitCommandsAI::GatherWait::New(const Command& cmd)
1019 {
1020 	GatherWait* gw = new GatherWait(cmd);
1021 	if (!gw->valid) {
1022 		delete gw;
1023 		return NULL;
1024 	}
1025 	return gw;
1026 }
1027 
1028 
GatherWait(const Command & cmd)1029 CWaitCommandsAI::GatherWait::GatherWait(const Command& cmd)
1030 : Wait(CMD_WAITCODE_GATHERWAIT)
1031 {
1032 	if (!cmd.params.empty()) {
1033 		return;
1034 	}
1035 
1036 	// only add valid units
1037 	const CUnitSet& selUnits = selectedUnitsHandler.selectedUnits;
1038 	CUnitSet::const_iterator sit;
1039 	for (sit = selUnits.begin(); sit != selUnits.end(); ++sit) {
1040 		CUnit* unit = *sit;
1041 		const UnitDef* ud = unit->unitDef;
1042 		if (ud->canmove && (dynamic_cast<CFactory*>(unit) == NULL)) {
1043 			waitUnits.insert(unit);
1044 		}
1045 	}
1046 
1047 	if (waitUnits.size() < 2) {
1048 		return; // one man does not a gathering make
1049 	}
1050 
1051 	valid = true;
1052 	key = GetNewKey();
1053 
1054 	Command waitCmd(CMD_WAIT, SHIFT_KEY, code);
1055 	waitCmd.PushParam(GetFloatFromKey(key));
1056 	selectedUnitsHandler.GiveCommand(waitCmd, true);
1057 
1058 	CUnitSet::iterator wit;
1059 	for (wit = waitUnits.begin(); wit != waitUnits.end(); ++wit) {
1060 		AddDeathDependence((CObject*)(*wit), DEPENDENCE_WAITCMD);
1061 	}
1062 
1063 	return;
1064 }
1065 
1066 
~GatherWait()1067 CWaitCommandsAI::GatherWait::~GatherWait()
1068 {
1069 	// do nothing
1070 }
1071 
1072 
DependentDied(CObject * object)1073 void CWaitCommandsAI::GatherWait::DependentDied(CObject* object)
1074 {
1075 	waitUnits.erase(static_cast<CUnit*>(object));
1076 }
1077 
1078 
AddUnit(CUnit * unit)1079 void CWaitCommandsAI::GatherWait::AddUnit(CUnit* unit)
1080 {
1081 	// do nothing
1082 }
1083 
1084 
RemoveUnit(CUnit * unit)1085 void CWaitCommandsAI::GatherWait::RemoveUnit(CUnit* unit)
1086 {
1087 	if (waitUnits.erase(unit))
1088 		DeleteDeathDependence(unit, DEPENDENCE_WAITCMD);
1089 }
1090 
1091 
Update()1092 void CWaitCommandsAI::GatherWait::Update()
1093 {
1094 	if (waitUnits.size() < 2) {
1095 		delete this;
1096 		return;
1097 	}
1098 
1099 	CUnitSet::iterator it = waitUnits.begin();
1100 	while (it != waitUnits.end()) {
1101 		WaitState state = GetWaitState(*it);
1102 		if (state == Active) {
1103 			// do nothing
1104 		}
1105 		else if(state == Queued) {
1106 			return;
1107 		}
1108 		else if (state == Missing) {
1109 			DeleteDeathDependence(*it, DEPENDENCE_WAITCMD);
1110 			it = RemoveUnitFromSet(it, waitUnits);
1111 			if (waitUnits.empty()) {
1112 				delete this;
1113 				return;
1114 			}
1115 			continue;
1116 		}
1117 		++it;
1118 	}
1119 
1120 	// all units are actively waiting on this command, unblock them and die
1121 	SendWaitCommand(waitUnits);
1122 	delete this;
1123 }
1124 
1125 
1126 /******************************************************************************/
1127