1 #include "FileMan.h"
2 #include "Font_Control.h"
3 #include "LoadSaveData.h"
4 #include "Types.h"
5 #include "Scheduling.h"
6 #include "Soldier_Control.h"
7 #include "Message.h"
8 #include "Overhead.h"
9 #include "Game_Clock.h"
10 #include "Game_Event_Hook.h"
11 #include "WorldDef.h"
12 #include "Soldier_Init_List.h"
13 #include "Isometric_Utils.h"
14 #include "AI.h"
15 #include "Debug.h"
16 #include "Random.h"
17 #include "Animation_Data.h"
18 #include "Map_Information.h"
19 #include "Keys.h"
20 #include "Structure_Wrap.h"
21 #include "JAScreens.h"
22 #include "StrategicMap.h"
23 #include "WorldMan.h"
24 #include "Soldier_Add.h"
25 #include "Animation_Control.h"
26 #include "Soldier_Profile.h"
27 #include "Quests.h"
28 #include "MemMan.h"
29 #include "ScreenIDs.h"
30 #include "GameState.h"
31 #include "Logger.h"
32 #include "EditorMercs.h"
33 
34 #include <string_theory/string>
35 
36 
37 #define FOURPM 960
38 
39 // waketime is the # of minutes in the day minus the sleep time
40 #define WAKETIME( x ) (NUM_SEC_IN_DAY/NUM_SEC_IN_MIN - x)
41 
42 //#define DISABLESCHEDULES
43 
44 SCHEDULENODE *gpScheduleList = NULL;
45 UINT8				gubScheduleID = 0;
46 
47 
48 //IMPORTANT:
49 //This function adds a NEWLY allocated schedule to the list.  The pointer passed is totally
50 //separate.  So make sure that you delete the pointer if you don't need it anymore.  The editor
51 //uses a single static node to copy data from, hence this method.
CopyScheduleToList(SCHEDULENODE * pSchedule,SOLDIERINITNODE * pNode)52 void CopyScheduleToList( SCHEDULENODE *pSchedule, SOLDIERINITNODE *pNode )
53 {
54 	SCHEDULENODE *curr;
55 	curr = gpScheduleList;
56 	gpScheduleList = new SCHEDULENODE{};
57 	*gpScheduleList = *pSchedule;
58 	gpScheduleList->next = curr;
59 	gubScheduleID++;
60 	//Assign all of the links
61 	gpScheduleList->ubScheduleID = gubScheduleID;
62 	gpScheduleList->soldier      = pNode->pSoldier;
63 	pNode->pDetailedPlacement->ubScheduleID = gubScheduleID;
64 	pNode->pSoldier->ubScheduleID = gubScheduleID;
65 	if( gubScheduleID > 40 )
66 	{ //Too much fragmentation, clean it up...
67 		OptimizeSchedules();
68 		if( gubScheduleID > 32 )
69 		{
70 			SLOGW("too many Schedules posted." );
71 		}
72 	}
73 }
74 
GetSchedule(UINT8 ubScheduleID)75 SCHEDULENODE* GetSchedule( UINT8 ubScheduleID )
76 {
77 	SCHEDULENODE *curr;
78 	curr = gpScheduleList;
79 	while( curr )
80 	{
81 		if( curr->ubScheduleID == ubScheduleID )
82 			return curr;
83 		curr = curr->next;
84 	}
85 	return NULL;
86 }
87 
88 //Removes all schedules from the event list, and cleans out the list.
DestroyAllSchedules()89 void DestroyAllSchedules()
90 {
91 	SCHEDULENODE *curr;
92 	//First remove all of the events.
93 	DeleteAllStrategicEventsOfType( EVENT_PROCESS_TACTICAL_SCHEDULE );
94 	//Now, delete all of the schedules.
95 	while( gpScheduleList )
96 	{
97 		curr = gpScheduleList;
98 		gpScheduleList = gpScheduleList->next;
99 		delete curr;
100 	}
101 	gpScheduleList = NULL;
102 	gubScheduleID = 0;
103 }
104 
105 // cleans out the schedule list without touching events, for saving & loading games
DestroyAllSchedulesWithoutDestroyingEvents()106 void DestroyAllSchedulesWithoutDestroyingEvents()
107 {
108 	SCHEDULENODE *curr;
109 
110 	//delete all of the schedules.
111 	while( gpScheduleList )
112 	{
113 		curr = gpScheduleList;
114 		gpScheduleList = gpScheduleList->next;
115 		delete curr;
116 	}
117 	gpScheduleList = NULL;
118 	gubScheduleID = 0;
119 }
120 
DeleteSchedule(UINT8 ubScheduleID)121 void DeleteSchedule( UINT8 ubScheduleID )
122 {
123 	SCHEDULENODE *curr, *temp = NULL;
124 
125 	if (!gpScheduleList)
126 	{
127 		SLOGW("Attempting to delete schedule that doesn't exist");
128 		return;
129 	}
130 
131 	curr = gpScheduleList;
132 
133 	if( gpScheduleList->ubScheduleID == ubScheduleID )
134 	{ //Deleting the head
135 		temp = gpScheduleList;
136 		gpScheduleList = gpScheduleList->next;
137 	}
138 	else while( curr->next )
139 	{
140 		if( curr->next->ubScheduleID == ubScheduleID )
141 		{
142 			temp = curr->next;
143 			curr->next = temp->next;
144 			break;
145 		}
146 		curr = curr->next;
147 	}
148 	if( temp )
149 	{
150 		DeleteStrategicEvent( EVENT_PROCESS_TACTICAL_SCHEDULE, temp->ubScheduleID );
151 		delete temp;
152 	}
153 }
154 
155 
156 static void PrepareScheduleForAutoProcessing(SCHEDULENODE* pSchedule, UINT32 uiStartTime, UINT32 uiEndTime);
157 
158 
ProcessTacticalSchedule(UINT8 ubScheduleID)159 void ProcessTacticalSchedule( UINT8 ubScheduleID )
160 {
161 	SCHEDULENODE *pSchedule;
162 	INT32 iScheduleIndex=0;
163 	BOOLEAN fAutoProcess;
164 
165 	//Attempt to locate the schedule.
166 	pSchedule = GetSchedule( ubScheduleID );
167 	if( !pSchedule )
168 	{
169 		SLOGW("Schedule callback:  Schedule ID of %d not found.", ubScheduleID );
170 		return;
171 	}
172 	//Attempt to access the soldier involved
173 	SOLDIERTYPE* const pSoldier = pSchedule->soldier;
174 	if (pSoldier == NULL)
175 	{
176 		SLOGW("Schedule callback:  Illegal NULL soldier.");
177 		return;
178 	}
179 
180 	//Validate the existance of the soldier.
181 	if ( pSoldier->bLife < OKLIFE )
182 	{
183 		// dead or dying!
184 		return;
185 	}
186 
187 	if ( !pSoldier->bActive )
188 	{
189 		SLOGW("Schedule callback:  Soldier isn't active.  Name is %s.", pSoldier->name.c_str());
190 	}
191 
192 	//Okay, now we have good pointers to the soldier and the schedule.
193 	//Now, determine which time in this schedule that we are processing.
194 	fAutoProcess = FALSE;
195 	if( guiCurrentScreen != GAME_SCREEN )
196 	{
197 		SLOGD("Schedule callback occurred outside of tactical -- Auto processing!" );
198 		fAutoProcess = TRUE;
199 	}
200 	else
201 	{
202 		for( iScheduleIndex = 0; iScheduleIndex < MAX_SCHEDULE_ACTIONS; iScheduleIndex++ )
203 		{
204 			if( pSchedule->usTime[ iScheduleIndex ] == GetWorldMinutesInDay() )
205 			{
206 				SLOGD("Processing schedule on time -- AI processing!" );
207 				break;
208 			}
209 		}
210 		if( iScheduleIndex == MAX_SCHEDULE_ACTIONS )
211 		{
212 			fAutoProcess = TRUE;
213 			SLOGD("Possible timewarp causing schedule callback to occur late -- Auto processing!" );
214 		}
215 	}
216 	if ( fAutoProcess )
217 	{
218 		UINT32 uiStartTime, uiEndTime;
219 		//Grab the last time the eventlist was queued.  This will tell us how much time has passed since that moment,
220 		//and how long we need to auto process this schedule.
221 		uiStartTime = (guiTimeOfLastEventQuery / 60) % NUM_MIN_IN_DAY;
222 		uiEndTime = GetWorldMinutesInDay();
223 		if( uiStartTime != uiEndTime )
224 		{
225 			PrepareScheduleForAutoProcessing( pSchedule, uiStartTime, uiEndTime );
226 		}
227 	}
228 	else
229 	{
230 		// turn off all active-schedule flags before setting
231 		// the one that should be active!
232 		pSchedule->usFlags &= ~SCHEDULE_FLAGS_ACTIVE_ALL;
233 
234 		switch( iScheduleIndex )
235 		{
236 			case 0:			pSchedule->usFlags |= SCHEDULE_FLAGS_ACTIVE1;			break;
237 			case 1:			pSchedule->usFlags |= SCHEDULE_FLAGS_ACTIVE2;			break;
238 			case 2:			pSchedule->usFlags |= SCHEDULE_FLAGS_ACTIVE3;			break;
239 			case 3:			pSchedule->usFlags |= SCHEDULE_FLAGS_ACTIVE4;			break;
240 		}
241 		pSoldier->fAIFlags |= AI_CHECK_SCHEDULE;
242 		pSoldier->bAIScheduleProgress = 0;
243 	}
244 
245 }
246 
247 //Called before leaving the editor, or saving the map.  This recalculates
248 //all of the schedule IDs from scratch and adjusts the effected structures accordingly.
OptimizeSchedules()249 void OptimizeSchedules()
250 {
251 	SCHEDULENODE *pSchedule;
252 	UINT8 ubOldScheduleID;
253 	gubScheduleID = 0;
254 	pSchedule = gpScheduleList;
255 	while( pSchedule )
256 	{
257 		gubScheduleID++;
258 		ubOldScheduleID = pSchedule->ubScheduleID;
259 		if( ubOldScheduleID != gubScheduleID )
260 		{ //The schedule ID has changed, so change all links accordingly.
261 			pSchedule->ubScheduleID = gubScheduleID;
262 			CFOR_EACH_SOLDIERINITNODE(pNode)
263 			{
264 				if( pNode->pDetailedPlacement && pNode->pDetailedPlacement->ubScheduleID == ubOldScheduleID )
265 				{
266 					//Temporarily add 100 to the ID number to ensure that it doesn't get used again later.
267 					//We will remove it immediately after this loop is complete.
268 					pNode->pDetailedPlacement->ubScheduleID = gubScheduleID + 100;
269 					if( pNode->pSoldier )
270 					{
271 						pNode->pSoldier->ubScheduleID = gubScheduleID;
272 					}
273 					break;
274 				}
275 			}
276 		}
277 		pSchedule = pSchedule->next;
278 	}
279 	//Remove the +100 IDs.
280 	CFOR_EACH_SOLDIERINITNODE(pNode)
281 	{
282 		if( pNode->pDetailedPlacement && pNode->pDetailedPlacement->ubScheduleID > 100 )
283 		{
284 			pNode->pDetailedPlacement->ubScheduleID -= 100;
285 		}
286 	}
287 }
288 
289 //Called when transferring from the game to the editor.
PrepareSchedulesForEditorEntry()290 void PrepareSchedulesForEditorEntry()
291 {
292 	SCHEDULENODE *curr, *prev, *temp;
293 
294 	//Delete all schedule events.  The editor will automatically warp all civilians to their starting locations.
295 	DeleteAllStrategicEventsOfType( EVENT_PROCESS_TACTICAL_SCHEDULE );
296 
297 	//Now, delete all of the temporary schedules.
298 	curr = gpScheduleList;
299 	prev = NULL;
300 	while( curr )
301 	{
302 		if( curr->usFlags & SCHEDULE_FLAGS_TEMPORARY )
303 		{
304 			if( prev )
305 				prev->next = curr->next;
306 			else
307 				gpScheduleList = gpScheduleList->next;
308 			curr->soldier->ubScheduleID = 0;
309 			temp = curr;
310 			curr = curr->next;
311 			delete temp;
312 			gubScheduleID--;
313 		}
314 		else
315 		{
316 			if( curr->usFlags & SCHEDULE_FLAGS_SLEEP_CONVERTED )
317 			{ //uncovert it!
318 				INT32 i;
319 				for( i = 0 ; i < MAX_SCHEDULE_ACTIONS; i++ )
320 				{
321 					//if( i
322 				}
323 			}
324 			prev = curr;
325 			curr = curr->next;
326 		}
327 	}
328 }
329 
330 //Called when leaving the editor to enter the game.  This posts all of the events that apply.
PrepareSchedulesForEditorExit()331 void PrepareSchedulesForEditorExit()
332 {
333 	PostSchedules();
334 }
335 
336 
LoadSchedules(HWFILE const f)337 void LoadSchedules(HWFILE const f)
338 {
339 	/* Delete all the schedules we might have loaded (though we shouldn't have any
340 		* loaded!) */
341 	if (gpScheduleList) DestroyAllSchedules();
342 
343 	UINT8 n_schedules;
344 	FileRead(f, &n_schedules, sizeof(n_schedules));
345 
346 	gubScheduleID = 1;
347 	SCHEDULENODE** anchor = &gpScheduleList;
348 	for (UINT8 n = n_schedules; n != 0; --n)
349 	{
350 		BYTE data[36];
351 		FileRead(f, data, sizeof(data));
352 
353 		SCHEDULENODE* const node = new SCHEDULENODE{};
354 
355 		DataReader d{data};
356 		EXTR_SKIP(d, 4)
357 		EXTR_U16A(d, node->usTime,   lengthof(node->usTime))
358 		EXTR_U16A(d, node->usData1,  lengthof(node->usData1))
359 		EXTR_U16A(d, node->usData2,  lengthof(node->usData2))
360 		EXTR_U8A( d, node->ubAction, lengthof(node->ubAction))
361 		EXTR_SKIP(d, 2) // skip schedule ID and soldier ID, they get overwritten
362 		EXTR_U16( d, node->usFlags)
363 		Assert(d.getConsumed() == lengthof(data));
364 
365 		node->ubScheduleID = gubScheduleID++;
366 
367 		// Add node to the list
368 		*anchor = node;
369 		anchor  = &node->next;
370 	}
371 	// Schedules are posted when the soldier is added
372 }
373 
374 
LoadSchedulesFromSave(HWFILE const f)375 void LoadSchedulesFromSave(HWFILE const f)
376 {
377 	UINT8 n_schedules_saved;
378 	FileRead(f, &n_schedules_saved, sizeof(n_schedules_saved));
379 
380 	// Hack problem with schedules getting misaligned.
381 	UINT32 n_schedules = n_schedules_saved;
382 
383 	gubScheduleID = 1;
384 	SCHEDULENODE** anchor = &gpScheduleList;
385 	for (; n_schedules != 0; --n_schedules)
386 	{
387 		BYTE data[36];
388 		FileRead(f, data, sizeof(data));
389 
390 		SCHEDULENODE* const node = new SCHEDULENODE{};
391 
392 		DataReader s{data};
393 		EXTR_SKIP(   s, 4)
394 		EXTR_U16A(   s, node->usTime,   lengthof(node->usTime))
395 		EXTR_U16A(   s, node->usData1,  lengthof(node->usData1))
396 		EXTR_U16A(   s, node->usData2,  lengthof(node->usData2))
397 		EXTR_U8A(    s, node->ubAction, lengthof(node->ubAction))
398 		EXTR_U8(     s, node->ubScheduleID)
399 		EXTR_SOLDIER(s, node->soldier)
400 		EXTR_U16(    s, node->usFlags)
401 		Assert(s.getConsumed() == lengthof(data));
402 
403 		// Add node to the list
404 		*anchor = node;
405 		anchor  = &node->next;
406 
407 		++gubScheduleID;
408 	}
409 	// Schedules are posted when the soldier is added
410 }
411 
412 
ReverseSchedules()413 void ReverseSchedules()
414 {
415 	SCHEDULENODE *pReverseHead, *pPrevReverseHead, *pPrevScheduleHead;
416 	UINT8	ubOppositeID = gubScheduleID;
417 	//First, remove any gaps which would mess up the reverse ID assignment by optimizing
418 	//the schedules.
419 	OptimizeSchedules();
420 	pReverseHead = NULL;
421 	while( gpScheduleList )
422 	{
423 		//reverse the ID
424 		gpScheduleList->ubScheduleID = ubOppositeID;
425 		ubOppositeID--;
426 		//detach current schedule head from list and advance it
427 		pPrevScheduleHead = gpScheduleList;
428 		gpScheduleList = gpScheduleList->next;
429 		//get previous reversed list head (even if null)
430 		pPrevReverseHead = pReverseHead;
431 		//Assign the previous schedule head to the reverse head
432 		pReverseHead = pPrevScheduleHead;
433 		//Point the next to the previous reverse head.
434 		pReverseHead->next = pPrevReverseHead;
435 	}
436 	//Now assign the schedule list to the reverse head.
437 	gpScheduleList = pReverseHead;
438 }
439 
440 //Another debug feature.
ClearAllSchedules()441 void ClearAllSchedules()
442 {
443 	DestroyAllSchedules();
444 	CFOR_EACH_SOLDIERINITNODE(pNode)
445 	{
446 		if( pNode->pDetailedPlacement && pNode->pDetailedPlacement->ubScheduleID )
447 		{
448 			pNode->pDetailedPlacement->ubScheduleID = 0;
449 			if( pNode->pSoldier )
450 			{
451 				pNode->pSoldier->ubScheduleID = 0;
452 			}
453 		}
454 	}
455 }
456 
457 
SaveSchedules(HWFILE const f)458 void SaveSchedules(HWFILE const f)
459 {
460 	// Count the number of schedules in the list
461 	INT32 n_schedules = 0;
462 	for (SCHEDULENODE const* i = gpScheduleList; i; i = i->next)
463 	{
464 		// Skip all default schedules
465 		if (i->usFlags & SCHEDULE_FLAGS_TEMPORARY) continue;
466 		++n_schedules;
467 	}
468 
469 	UINT8 n_to_save = MIN(n_schedules, 32);
470 	FileWrite(f, &n_to_save, sizeof(UINT8));
471 
472 	// Save each schedule
473 	for (SCHEDULENODE const* i = gpScheduleList; i; i = i->next)
474 	{
475 		// Skip all default schedules
476 		if (i->usFlags & SCHEDULE_FLAGS_TEMPORARY) continue;
477 
478 		if (n_to_save-- == 0) return;
479 
480 		BYTE data[36];
481 		DataWriter d{data};
482 		INJ_SKIP(   d, 4)
483 		INJ_U16A(   d, i->usTime,   lengthof(i->usTime))
484 		INJ_U16A(   d, i->usData1,  lengthof(i->usData1))
485 		INJ_U16A(   d, i->usData2,  lengthof(i->usData2))
486 		INJ_U8A(    d, i->ubAction, lengthof(i->ubAction))
487 		INJ_U8(     d, i->ubScheduleID)
488 		INJ_SOLDIER(d, i->soldier)
489 		INJ_U16(    d, i->usFlags)
490 		Assert(d.getConsumed() == lengthof(data));
491 
492 		FileWrite(f, data, sizeof(data));
493 	}
494 }
495 
496 
497 //Each schedule has upto four parts to it, so sort them chronologically.
498 //Happily, the fields with no times actually are the highest.
SortSchedule(SCHEDULENODE * pSchedule)499 BOOLEAN SortSchedule( SCHEDULENODE *pSchedule )
500 {
501 	INT32 index, i, iBestIndex;
502 	UINT16 usTime;
503 	UINT16 usData1;
504 	UINT16 usData2;
505 	UINT8 ubAction;
506 	BOOLEAN fSorted = FALSE;
507 
508 	//Use a bubblesort method (max:  3 switches).
509 	index = 0;
510 	while( index < 3 )
511 	{
512 		usTime = 0xffff;
513 		iBestIndex = index;
514 		for( i = index; i < MAX_SCHEDULE_ACTIONS; i++ )
515 		{
516 			if( pSchedule->usTime[ i ] < usTime )
517 			{
518 				usTime = pSchedule->usTime[ i ];
519 				iBestIndex = i;
520 			}
521 		}
522 		if( iBestIndex != index )
523 		{ //we will swap the best index with the current index.
524 			fSorted = TRUE;
525 			usTime		= pSchedule->usTime[ index ];
526 			usData1		= pSchedule->usData1[ index ];
527 			usData2		= pSchedule->usData2[ index ];
528 			ubAction	= pSchedule->ubAction[ index ];
529 			pSchedule->usTime[ index ]		= pSchedule->usTime[ iBestIndex ];
530 			pSchedule->usData1[ index ]		= pSchedule->usData1[ iBestIndex ];
531 			pSchedule->usData2[ index ]		= pSchedule->usData2[ iBestIndex ];
532 			pSchedule->ubAction[ index ]	= pSchedule->ubAction[ iBestIndex ];
533 			pSchedule->usTime[ iBestIndex ]		= usTime;
534 			pSchedule->usData1[ iBestIndex ]	= usData1;
535 			pSchedule->usData2[ iBestIndex ]	= usData2;
536 			pSchedule->ubAction[ iBestIndex ]	= ubAction;
537 		}
538 		index++;
539 	}
540 	return fSorted;
541 }
542 
BumpAnyExistingMerc(INT16 sGridNo)543 BOOLEAN BumpAnyExistingMerc( INT16 sGridNo )
544 {
545 	// this is for autoprocessing schedules...
546 	// there could be someone in the destination location, in which case
547 	// we want to 'bump' them to the nearest available spot
548 
549 	if ( !GridNoOnVisibleWorldTile( sGridNo ) )
550 	{
551 		return( TRUE );
552 	}
553 
554 	SOLDIERTYPE* const pSoldier = WhoIsThere2(sGridNo, 0);
555 	if (pSoldier == NULL) return TRUE;
556 
557 	// what if the existing merc is prone?
558 	const INT16 sNewGridNo = FindGridNoFromSweetSpotWithStructDataFromSoldier(pSoldier, STANDING, 5, 1, pSoldier);
559 	//const INT16 sNewGridNo = FindGridNoFromSweetSpotExcludingSweetSpot(pSoldier, sGridNo, 10);
560 
561 	if ( sNewGridNo == NOWHERE )
562 	{
563 		return( FALSE );
564 	}
565 
566 	EVENT_SetSoldierPositionNoCenter(pSoldier, sNewGridNo, SSP_FORCE_DELETE);
567 
568 	return( TRUE );
569 }
570 
571 
572 static void PerformActionOnDoorAdjacentToGridNo(UINT8 ubScheduleAction, UINT16 usGridNo);
573 
574 
AutoProcessSchedule(SCHEDULENODE * pSchedule,INT32 index)575 static void AutoProcessSchedule(SCHEDULENODE* pSchedule, INT32 index)
576 {
577 	INT8						bDirection;
578 
579 	if ( gTacticalStatus.uiFlags & LOADING_SAVED_GAME )
580 	{
581 		// CJC, November 28th:  when reloading a saved game we want events posted but no events autoprocessed since
582 		// that could change civilian positions.  So rather than doing a bunch of checks outside of this function,
583 		// I thought it easier to screen them out here.
584 		return;
585 	}
586 
587 	SOLDIERTYPE* const pSoldier = pSchedule->soldier;
588 
589 	if(GameState::getInstance()->isEditorMode())
590 	{
591 		if ( pSoldier->ubProfile != NO_PROFILE )
592 		{
593 				SLOGD("Autoprocessing schedule action %s for %s (%d) at time %02ld:%02ld (set for %02d:%02d), data1 = %d",
594 				gszScheduleActions[ pSchedule->ubAction[ index ] ].c_str(),
595 				pSoldier->name.c_str(),
596 				pSoldier->ubID,
597 				GetWorldHour(),
598 				guiMin,
599 				pSchedule->usTime[ index ] / 60,
600 				pSchedule->usTime[ index ] % 60,
601 				pSchedule->usData1[ index ]);
602 		}
603 		else
604 		{
605 			SLOGD("Autoprocessing schedule action %s for civ (%d) at time %02ld:%02ld (set for %02d:%02d), data1 = %d",
606 				gszScheduleActions[ pSchedule->ubAction[ index ] ].c_str(),
607 				pSoldier->ubID,
608 				GetWorldHour(),
609 				guiMin,
610 				pSchedule->usTime[ index ] / 60,
611 				pSchedule->usTime[ index ] % 60,
612 				pSchedule->usData1[ index ]);
613 		}
614 	}
615 
616 	// always assume the merc is going to wake, unless the event is a sleep
617 	pSoldier->fAIFlags &= ~(AI_ASLEEP);
618 
619 	switch( pSchedule->ubAction[ index ] )
620 	{
621 		case SCHEDULE_ACTION_LOCKDOOR:
622 		case SCHEDULE_ACTION_UNLOCKDOOR:
623 		case SCHEDULE_ACTION_OPENDOOR:
624 		case SCHEDULE_ACTION_CLOSEDOOR:
625 			PerformActionOnDoorAdjacentToGridNo( pSchedule->ubAction[ index ], pSchedule->usData1[ index ] );
626 			BumpAnyExistingMerc( pSchedule->usData2[ index ] );
627 
628 			EVENT_SetSoldierPositionNoCenter(pSoldier, pSchedule->usData2[index], SSP_FORCE_DELETE);
629 			if ( GridNoOnEdgeOfMap( pSchedule->usData2[ index ], &bDirection ) )
630 			{
631 				// civ should go off map; this tells us where the civ will return
632 				pSoldier->sOffWorldGridNo = pSchedule->usData2[ index ];
633 				MoveSoldierFromMercToAwaySlot( pSoldier );
634 				pSoldier->bInSector = FALSE;
635 			}
636 			else
637 			{
638 				// let this person patrol from here from now on
639 				pSoldier->usPatrolGrid[0] = pSchedule->usData2[ index ];
640 			}
641 			break;
642 		case SCHEDULE_ACTION_GRIDNO:
643 			BumpAnyExistingMerc( pSchedule->usData1[ index ] );
644 			EVENT_SetSoldierPositionNoCenter(pSoldier, pSchedule->usData1[index], SSP_FORCE_DELETE);
645 			// let this person patrol from here from now on
646 			pSoldier->usPatrolGrid[0] = pSchedule->usData1[ index ];
647 			break;
648 		case SCHEDULE_ACTION_ENTERSECTOR:
649 			if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags2 & PROFILE_MISC_FLAG2_DONT_ADD_TO_SECTOR )
650 			{
651 				// never process enter if flag is set
652 				break;
653 			}
654 			BumpAnyExistingMerc( pSchedule->usData1[ index ] );
655 			EVENT_SetSoldierPositionNoCenter(pSoldier, pSchedule->usData1[index], SSP_FORCE_DELETE);
656 			MoveSoldierFromAwayToMercSlot( pSoldier );
657 			pSoldier->bInSector = TRUE;
658 			// let this person patrol from here from now on
659 			pSoldier->usPatrolGrid[0] = pSchedule->usData1[ index ];
660 			break;
661 		case SCHEDULE_ACTION_WAKE:
662 			BumpAnyExistingMerc( pSoldier->sInitialGridNo );
663 			EVENT_SetSoldierPositionNoCenter(pSoldier, pSoldier->sInitialGridNo, SSP_FORCE_DELETE);
664 			// let this person patrol from here from now on
665 			pSoldier->usPatrolGrid[0] = pSoldier->sInitialGridNo;
666 			break;
667 		case SCHEDULE_ACTION_SLEEP:
668 			pSoldier->fAIFlags |= AI_ASLEEP;
669 			// check for someone else in the location
670 			BumpAnyExistingMerc( pSchedule->usData1[ index ] );
671 			EVENT_SetSoldierPositionNoCenter(pSoldier, pSchedule->usData1[index], SSP_FORCE_DELETE);
672 			pSoldier->usPatrolGrid[0] = pSchedule->usData1[ index ];
673 			break;
674 		case SCHEDULE_ACTION_LEAVESECTOR:
675 		{
676 			INT16 sGridNo;
677 			sGridNo = FindNearestEdgePoint( pSoldier->sGridNo );
678 			BumpAnyExistingMerc( sGridNo );
679 			EVENT_SetSoldierPositionNoCenter(pSoldier, sGridNo, SSP_FORCE_DELETE);
680 
681 			sGridNo = FindNearbyPointOnEdgeOfMap( pSoldier, &bDirection );
682 			BumpAnyExistingMerc( sGridNo );
683 			EVENT_SetSoldierPositionNoCenter(pSoldier, sGridNo, SSP_FORCE_DELETE);
684 
685 			// ok, that tells us where the civ will return
686 			pSoldier->sOffWorldGridNo = sGridNo;
687 			MoveSoldierFromMercToAwaySlot( pSoldier );
688 			pSoldier->bInSector = FALSE;
689 			break;
690 		}
691 	}
692 }
693 
694 
695 static INT8 GetEmptyScheduleEntry(SCHEDULENODE* pSchedule);
696 static BOOLEAN ScheduleHasMorningNonSleepEntries(SCHEDULENODE* pSchedule);
697 static void SecureSleepSpot(SOLDIERTYPE* pSoldier, UINT16 usSleepSpot);
698 
699 
PostSchedule(SOLDIERTYPE * pSoldier)700 static void PostSchedule(SOLDIERTYPE* pSoldier)
701 {
702 	UINT32 uiStartTime, uiEndTime;
703 	INT32 i;
704 	INT8	bEmpty;
705 	SCHEDULENODE *pSchedule;
706 	UINT8	ubTempAction;
707 	UINT16	usTemp;
708 
709 	if ( (pSoldier->ubCivilianGroup == KINGPIN_CIV_GROUP) && ( gTacticalStatus.fCivGroupHostile[ KINGPIN_CIV_GROUP ] || ( (gubQuest[ QUEST_KINGPIN_MONEY ] == QUESTINPROGRESS) && (CheckFact( FACT_KINGPIN_CAN_SEND_ASSASSINS, KINGPIN )) ) ) && (gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_C) && (pSoldier->ubProfile == NO_PROFILE) )
710 	{
711 		// no schedules for people guarding Tony's!
712 		return;
713 	}
714 
715 	pSchedule = GetSchedule( pSoldier->ubScheduleID );
716 	if( !pSchedule )
717 		return;
718 
719 	if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 & PROFILE_MISC_FLAG3_PERMANENT_INSERTION_CODE )
720 	{
721 		// don't process schedule
722 		return;
723 	}
724 
725 	//if this schedule doesn't have a time associated with it, then generate a time, but only
726 	//if it is a sleep schedule.
727 	for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
728 	{
729 		if ( pSchedule->ubAction[ i ] == SCHEDULE_ACTION_SLEEP )
730 		{
731 			// first make sure that this merc has a unique spot to sleep in
732 			SecureSleepSpot( pSoldier, pSchedule->usData1[ i ] );
733 
734 			if( pSchedule->usTime[ i ] == 0xffff )
735 			{
736 				pSchedule->usTime[ i ] = (UINT16)( (21*60) + Random( (3*60) )); //9PM - 11:59PM
737 
738 				if ( ScheduleHasMorningNonSleepEntries( pSchedule ) )
739 				{
740 					// this guy will sleep until the next non-sleep event
741 				}
742 				else
743 				{
744 					bEmpty = GetEmptyScheduleEntry( pSchedule );
745 					if ( bEmpty != -1 )
746 					{
747 						// there is an empty entry for the wakeup call
748 
749 						// NB the wakeup call must be ordered first! so we have to create the
750 						// wake action and then swap the two.
751 						pSchedule->ubAction[ bEmpty ] = SCHEDULE_ACTION_WAKE;
752 						pSchedule->usTime[ bEmpty ] = (pSchedule->usTime[ i ] + (8*60)) % NUM_MIN_IN_DAY; // sleep for 8 hours
753 
754 						ubTempAction = pSchedule->ubAction[ bEmpty ];
755 						pSchedule->ubAction[ bEmpty ] = pSchedule->ubAction[ i ];
756 						pSchedule->ubAction[ i ] = ubTempAction;
757 
758 						usTemp = pSchedule->usTime[ bEmpty ];
759 						pSchedule->usTime[ bEmpty ] = pSchedule->usTime[ i ];
760 						pSchedule->usTime[ i ] = usTemp;
761 
762 						usTemp = pSchedule->usData1[ bEmpty ];
763 						pSchedule->usData1[ bEmpty ] = pSchedule->usData1[ i ];
764 						pSchedule->usData1[ i ] = usTemp;
765 
766 						usTemp = pSchedule->usData2[ bEmpty ];
767 						pSchedule->usData2[ bEmpty ] = pSchedule->usData2[ i ];
768 						pSchedule->usData2[ i ] = usTemp;
769 					}
770 					else
771 					{
772 						// no morning entries but no space for a wakeup either, will sleep till
773 						// next non-sleep event
774 					}
775 
776 				}
777 				break; //The break is here because nobody should have more than one sleep schedule with no time specified.
778 			}
779 		}
780 	}
781 
782 	pSchedule->soldier = pSoldier;
783 
784 	// always process previous 24 hours
785 	uiEndTime = GetWorldTotalMin();
786 	uiStartTime = uiEndTime - (NUM_MIN_IN_DAY - 1);
787 
788 	/*
789 	//First thing we need is to get the time that the map was last loaded.  If more than 24 hours,
790 	//then process only 24 hours.  If less, then process all the schedules that would have happened within
791 	//that period of time.
792 	uiEndTime = GetWorldTotalMin();
793 	if( GetWorldTotalMin() - guiTimeCurrentSectorWasLastLoaded > NUM_MIN_IN_DAY )
794 	{ //Process the last 24 hours
795 		uiStartTime = uiEndTime - (NUM_MIN_IN_DAY - 1);
796 	}
797 	else
798 	{ //Process the time since we were last here.
799 		uiStartTime = guiTimeCurrentSectorWasLastLoaded;
800 	}
801 	*/
802 
803 	//Need a way to determine if the player has actually modified doors since this civilian was last loaded
804 	uiEndTime %= NUM_MIN_IN_DAY;
805 	uiStartTime %= NUM_MIN_IN_DAY;
806 	PrepareScheduleForAutoProcessing( pSchedule, uiStartTime, uiEndTime );
807 }
808 
809 
PrepareScheduleForAutoProcessing(SCHEDULENODE * pSchedule,UINT32 uiStartTime,UINT32 uiEndTime)810 static void PrepareScheduleForAutoProcessing(SCHEDULENODE* pSchedule, UINT32 uiStartTime, UINT32 uiEndTime)
811 {
812 	INT32 i;
813 	BOOLEAN	fPostedNextEvent = FALSE;
814 
815 	if ( uiStartTime > uiEndTime )
816 	{ //The start time is later in the day than the end time, which means we'll be wrapping
817 		//through midnight and continuing to the end time.
818 		for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
819 		{
820 			if( pSchedule->usTime[i] == 0xffff )
821 				break;
822 			if( pSchedule->usTime[i] >= uiStartTime )
823 			{
824 				AutoProcessSchedule( pSchedule, i );
825 			}
826 		}
827 		for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
828 		{
829 			if( pSchedule->usTime[i] == 0xffff )
830 				break;
831 			if( pSchedule->usTime[i] <= uiEndTime )
832 			{
833 				AutoProcessSchedule( pSchedule, i );
834 			}
835 			else
836 			{
837 				// CJC: Note that end time is always passed in here as the current time so GetWorldDayInMinutes will be for the correct day
838 				AddStrategicEvent( EVENT_PROCESS_TACTICAL_SCHEDULE, GetWorldDayInMinutes() + pSchedule->usTime[i], pSchedule->ubScheduleID );
839 				fPostedNextEvent = TRUE;
840 				break;
841 			}
842 		}
843 	}
844 	else
845 	{ //Much simpler:  start at the start and continue to the end.
846 		for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
847 		{
848 			if( pSchedule->usTime[i] == 0xffff )
849 				break;
850 
851 			if( pSchedule->usTime[i] >= uiStartTime && pSchedule->usTime[i] <= uiEndTime )
852 			{
853 				AutoProcessSchedule( pSchedule, i );
854 			}
855 			else if ( pSchedule->usTime[i] >= uiEndTime )
856 			{
857 				fPostedNextEvent = TRUE;
858 				AddStrategicEvent( EVENT_PROCESS_TACTICAL_SCHEDULE, GetWorldDayInMinutes() + pSchedule->usTime[i], pSchedule->ubScheduleID );
859 				break;
860 			}
861 
862 		}
863 	}
864 
865 	if ( !fPostedNextEvent )
866 	{
867 		// reached end of schedule, post first event for soldier in the next day
868 		// 0th event will be first.
869 		// Feb 1:  ONLY IF THERE IS A VALID EVENT TO POST WITH A VALID TIME!
870 		if ( pSchedule->usTime[0] != 0xffff )
871 		{
872 			AddStrategicEvent( EVENT_PROCESS_TACTICAL_SCHEDULE, GetWorldDayInMinutes() + NUM_MIN_IN_DAY + pSchedule->usTime[0], pSchedule->ubScheduleID );
873 		}
874 	}
875 }
876 
877 
878 //Leave at night, come back in the morning.  The time variances are a couple hours, so
879 //the town doesn't turn into a ghost town in 5 minutes.
PostDefaultSchedule(SOLDIERTYPE * pSoldier)880 static void PostDefaultSchedule(SOLDIERTYPE* pSoldier)
881 {
882 	INT32 i;
883 	SCHEDULENODE *curr;
884 
885 	if( gbWorldSectorZ )
886 	{ //People in underground sectors don't get schedules.
887 		return;
888 	}
889 	//Create a new node at the head of the list.  The head will become the new schedule
890 	//we are about to add.
891 	curr = gpScheduleList;
892 	gpScheduleList = new SCHEDULENODE{};
893 	gpScheduleList->next = curr;
894 	gubScheduleID++;
895 	//Assign all of the links
896 	gpScheduleList->ubScheduleID = gubScheduleID;
897 	gpScheduleList->soldier      = pSoldier;
898 	pSoldier->ubScheduleID = gubScheduleID;
899 
900 	//Clear the data inside the schedule
901 	for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
902 	{
903 		gpScheduleList->usTime[i] = 0xffff;
904 		gpScheduleList->usData1[i] = 0xffff;
905 		gpScheduleList->usData2[i] = 0xffff;
906 	}
907 	//Have the default schedule enter between 7AM and 8AM
908 	gpScheduleList->ubAction[0] = SCHEDULE_ACTION_ENTERSECTOR;
909 	gpScheduleList->usTime[0] = (UINT16)(420 + Random( 61 ));
910 	gpScheduleList->usData1[0] = pSoldier->sInitialGridNo;
911 	//Have the default schedule leave between 6PM and 8PM
912 	gpScheduleList->ubAction[1] = SCHEDULE_ACTION_LEAVESECTOR;
913 	gpScheduleList->usTime[1] = (UINT16)(1080 + Random( 121 ));
914 	gpScheduleList->usFlags |= SCHEDULE_FLAGS_TEMPORARY;
915 
916 	if( gubScheduleID == 255 )
917 	{ //Too much fragmentation, clean it up...
918 		OptimizeSchedules();
919 		if( gubScheduleID == 255 )
920 		{
921 			SLOGA("Too many schedules posted" );
922 		}
923 	}
924 
925 	PostSchedule( pSoldier );
926 }
927 
928 
PostSchedules()929 void PostSchedules()
930 {
931 	BOOLEAN fDefaultSchedulesPossible = FALSE;
932 
933 	#if defined( DISABLESCHEDULES ) //definition found at top of this .c file.
934 
935 		return;
936 
937 	#endif
938 	//If no way to leave the map, then don't post default schedules.
939 	if( gMapInformation.sNorthGridNo != -1 || gMapInformation.sEastGridNo != -1 ||
940 		gMapInformation.sSouthGridNo != -1 || gMapInformation.sWestGridNo != -1 )
941 	{
942 		fDefaultSchedulesPossible = TRUE;
943 	}
944 	CFOR_EACH_SOLDIERINITNODE(curr)
945 	{
946 		if( curr->pSoldier && curr->pSoldier->bTeam == CIV_TEAM )
947 		{
948 			if( curr->pDetailedPlacement && curr->pDetailedPlacement->ubScheduleID )
949 			{
950 				PostSchedule( curr->pSoldier );
951 			}
952 			else if( fDefaultSchedulesPossible )
953 			{
954 				// ATE: There should be a better way here...
955 				if( curr->pSoldier->ubBodyType != COW &&
956 					curr->pSoldier->ubBodyType != BLOODCAT &&
957 					curr->pSoldier->ubBodyType != HUMVEE &&
958 					curr->pSoldier->ubBodyType != ELDORADO &&
959 					curr->pSoldier->ubBodyType != ICECREAMTRUCK &&
960 					curr->pSoldier->ubBodyType != JEEP )
961 				{
962 					PostDefaultSchedule( curr->pSoldier );
963 				}
964 			}
965 		}
966 	}
967 }
968 
969 
PerformActionOnDoorAdjacentToGridNo(UINT8 ubScheduleAction,UINT16 usGridNo)970 static void PerformActionOnDoorAdjacentToGridNo(UINT8 ubScheduleAction, UINT16 usGridNo)
971 {
972 	INT16			sDoorGridNo;
973 	DOOR *		pDoor;
974 
975 	sDoorGridNo = FindDoorAtGridNoOrAdjacent( (INT16) usGridNo );
976 	if (sDoorGridNo != NOWHERE)
977 	{
978 		switch( ubScheduleAction )
979 		{
980 			case SCHEDULE_ACTION_LOCKDOOR:
981 				pDoor = FindDoorInfoAtGridNo( sDoorGridNo );
982 				if (pDoor)
983 				{
984 					pDoor->fLocked = TRUE;
985 				}
986 				// make sure it's closed as well
987 				ModifyDoorStatus( sDoorGridNo, FALSE, DONTSETDOORSTATUS );
988 				break;
989 			case SCHEDULE_ACTION_UNLOCKDOOR:
990 				pDoor = FindDoorInfoAtGridNo( sDoorGridNo );
991 				if (pDoor)
992 				{
993 					pDoor->fLocked = FALSE;
994 				}
995 				break;
996 			case SCHEDULE_ACTION_OPENDOOR:
997 				ModifyDoorStatus( sDoorGridNo, TRUE, DONTSETDOORSTATUS );
998 				break;
999 			case SCHEDULE_ACTION_CLOSEDOOR:
1000 				ModifyDoorStatus( sDoorGridNo, FALSE, DONTSETDOORSTATUS );
1001 				break;
1002 		}
1003 	}
1004 }
1005 
1006 //Assumes that a schedule has just been processed.  This takes the current time, and compares it to the
1007 //schedule, and looks for the next schedule action that would get processed and posts it.
PostNextSchedule(SOLDIERTYPE * pSoldier)1008 void PostNextSchedule( SOLDIERTYPE *pSoldier )
1009 {
1010 	SCHEDULENODE *pSchedule;
1011 	INT32 i, iBestIndex;
1012 	UINT16 usTime, usBestTime;
1013 	pSchedule = GetSchedule( pSoldier->ubScheduleID );
1014 	if( !pSchedule )
1015 	{ //post default?
1016 		return;
1017 	}
1018 	usTime = (UINT16)GetWorldMinutesInDay();
1019 	usBestTime = 0xffff;
1020 	iBestIndex = -1;
1021 	for( i = 0; i < MAX_SCHEDULE_ACTIONS; i++ )
1022 	{
1023 		if( pSchedule->usTime[i] == 0xffff )
1024 			continue;
1025 		if( pSchedule->usTime[i] == usTime )
1026 			continue;
1027 		if( pSchedule->usTime[i] > usTime )
1028 		{
1029 			if( pSchedule->usTime[i] - usTime < usBestTime )
1030 			{
1031 				usBestTime = pSchedule->usTime[ i ] - usTime;
1032 				iBestIndex = i;
1033 			}
1034 		}
1035 		else if ( (NUM_MIN_IN_DAY - (usTime - pSchedule->usTime[ i ])) < usBestTime )
1036 		{
1037 			usBestTime = NUM_MIN_IN_DAY - (usTime - pSchedule->usTime[ i ]);
1038 			iBestIndex = i;
1039 		}
1040 	}
1041 	Assert( iBestIndex >= 0 );
1042 
1043 	AddStrategicEvent( EVENT_PROCESS_TACTICAL_SCHEDULE, GetWorldDayInMinutes() + pSchedule->usTime[iBestIndex], pSchedule->ubScheduleID );
1044 }
1045 
1046 
1047 // This is for determining shopkeeper's opening/closing hours
ExtractScheduleDoorLockAndUnlockInfo(SOLDIERTYPE * pSoldier,UINT32 * puiOpeningTime,UINT32 * puiClosingTime)1048 BOOLEAN ExtractScheduleDoorLockAndUnlockInfo( SOLDIERTYPE * pSoldier, UINT32 * puiOpeningTime, UINT32 * puiClosingTime )
1049 {
1050 	INT32			iLoop;
1051 	BOOLEAN		fFoundOpeningTime = FALSE, fFoundClosingTime = FALSE;
1052 	SCHEDULENODE *pSchedule;
1053 
1054 	*puiOpeningTime = 0;
1055 	*puiClosingTime = 0;
1056 
1057 	pSchedule = GetSchedule( pSoldier->ubScheduleID );
1058 	if ( !pSchedule )
1059 	{
1060 		// If person had default schedule then would have been assigned and this would
1061 		// have succeeded.
1062 		// Hence this is an error.
1063 		return( FALSE );
1064 	}
1065 
1066 	for ( iLoop = 0; iLoop < MAX_SCHEDULE_ACTIONS; iLoop++ )
1067 	{
1068 		if ( pSchedule->ubAction[ iLoop ] == SCHEDULE_ACTION_UNLOCKDOOR )
1069 		{
1070 			fFoundOpeningTime = TRUE;
1071 			*puiOpeningTime = pSchedule->usTime[ iLoop ];
1072 		}
1073 		else if ( pSchedule->ubAction[ iLoop ] == SCHEDULE_ACTION_LOCKDOOR )
1074 		{
1075 			fFoundClosingTime = TRUE;
1076 			*puiClosingTime = pSchedule->usTime[ iLoop ];
1077 		}
1078 	}
1079 
1080 	if ( fFoundOpeningTime && fFoundClosingTime )
1081 	{
1082 		return( TRUE );
1083 	}
1084 	else
1085 	{
1086 		return( FALSE );
1087 	}
1088 }
1089 
1090 
ScheduleHasMorningNonSleepEntries(SCHEDULENODE * pSchedule)1091 static BOOLEAN ScheduleHasMorningNonSleepEntries(SCHEDULENODE* pSchedule)
1092 {
1093 	INT8			bLoop;
1094 
1095 	for ( bLoop = 0; bLoop < MAX_SCHEDULE_ACTIONS; bLoop++ )
1096 	{
1097 		if ( pSchedule->ubAction[ bLoop ] != SCHEDULE_ACTION_NONE && pSchedule->ubAction[ bLoop ] != SCHEDULE_ACTION_SLEEP )
1098 		{
1099 			if ( pSchedule->usTime[ bLoop ] < (12*60) )
1100 			{
1101 				return( TRUE );
1102 			}
1103 		}
1104 	}
1105 	return( FALSE );
1106 }
1107 
1108 
GetEmptyScheduleEntry(SCHEDULENODE * pSchedule)1109 static INT8 GetEmptyScheduleEntry(SCHEDULENODE* pSchedule)
1110 {
1111 	INT8			bLoop;
1112 
1113 	for ( bLoop = 0; bLoop < MAX_SCHEDULE_ACTIONS; bLoop++ )
1114 	{
1115 		if ( pSchedule->ubAction[ bLoop ] == SCHEDULE_ACTION_NONE )
1116 		{
1117 			return( bLoop );
1118 		}
1119 	}
1120 
1121 	return( -1 );
1122 }
1123 
1124 
FindSleepSpot(SCHEDULENODE * pSchedule)1125 static UINT16 FindSleepSpot(SCHEDULENODE* pSchedule)
1126 {
1127 	INT8			bLoop;
1128 
1129 	for ( bLoop = 0; bLoop < MAX_SCHEDULE_ACTIONS; bLoop++ )
1130 	{
1131 		if ( pSchedule->ubAction[ bLoop ] == SCHEDULE_ACTION_SLEEP )
1132 		{
1133 			return( pSchedule->usData1[ bLoop ] );
1134 		}
1135 	}
1136 	return( NOWHERE );
1137 }
1138 
1139 
ReplaceSleepSpot(SCHEDULENODE * pSchedule,UINT16 usNewSpot)1140 static void ReplaceSleepSpot(SCHEDULENODE* pSchedule, UINT16 usNewSpot)
1141 {
1142 	INT8			bLoop;
1143 
1144 	for ( bLoop = 0; bLoop < MAX_SCHEDULE_ACTIONS; bLoop++ )
1145 	{
1146 		if ( pSchedule->ubAction[ bLoop ] == SCHEDULE_ACTION_SLEEP )
1147 		{
1148 			pSchedule->usData1[ bLoop ] = usNewSpot;
1149 			break;
1150 		}
1151 	}
1152 }
1153 
1154 
SecureSleepSpot(SOLDIERTYPE * const pSoldier,UINT16 const usSleepSpot)1155 static void SecureSleepSpot(SOLDIERTYPE* const pSoldier, UINT16 const usSleepSpot)
1156 {
1157 	// start after this soldier's ID so we don't duplicate work done in previous passes
1158 	for (UINT32 i = pSoldier->ubID + 1; i <= gTacticalStatus.Team[CIV_TEAM].bLastID; ++i)
1159 	{
1160 		SOLDIERTYPE const& s2 = GetMan(i);
1161 		if (!s2.bActive || !s2.bInSector || s2.ubScheduleID == 0) continue;
1162 
1163 		SCHEDULENODE* const pSchedule = GetSchedule(s2.ubScheduleID);
1164 		if (!pSchedule) continue;
1165 
1166 		UINT16 const usSleepSpot2 = FindSleepSpot(pSchedule);
1167 		if (usSleepSpot2 != usSleepSpot) continue;
1168 
1169 		// conflict!
1170 		//UINT8 ubDirection;
1171 		//const UINT16 usNewSleepSpot = FindGridNoFromSweetSpotWithStructData(&s2, s2.usAnimState, usSleepSpot2, 3, &ubDirection, FALSE);
1172 		UINT16 const usNewSleepSpot = FindGridNoFromSweetSpotExcludingSweetSpot(&s2, usSleepSpot2, 3);
1173 		if (usNewSleepSpot == NOWHERE) continue;
1174 
1175 		ReplaceSleepSpot(pSchedule, usNewSleepSpot);
1176 	}
1177 }
1178