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