Advertisement
kregano

WOTC Will+Fatigue system

Sep 21st, 2017
967
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.90 KB | None | 0 0
  1. //---------------------------------------------------------------------------------------
  2. // FILE: X2EventListener_DefaultWillEvents.uc
  3. // AUTHOR: David Burchanowski
  4. //
  5. //---------------------------------------------------------------------------------------
  6. // Copyright (c) 2016 Firaxis Games, Inc. All rights reserved.
  7. //---------------------------------------------------------------------------------------
  8.  
  9. class XComGameStateContext_WillRoll extends XComGameStateContext
  10. config(GameCore)
  11. native(Core);
  12.  
  13. enum WillEventRoll_StatType
  14. {
  15. WillEventRollStat_None,
  16. WillEventRollStat_PercentageHealthLost,
  17. WillEventRollStat_SquadmateRank,
  18. WillEventRollStat_MaxWill,
  19. WillEventRollStat_BondLevel,
  20. WillEventRollStat_Flat
  21. };
  22.  
  23. struct native WillEventRollData_PanicWeight
  24. {
  25. var name PanicAbilityName;
  26. var float Weight; // 0.0-1.0
  27.  
  28. structdefaultproperties
  29. {
  30. Weight = 1
  31. }
  32. };
  33.  
  34. // This structure provides a simple uniform mechanism for doing rolls to lose will
  35. struct native WillEventRollData
  36. {
  37. var EMentalState MinimumTiredState; // this will data will not be applied to units that are less fatigued than this state
  38. var float WillEventActivationChance; // if all other conditions are met, this is the chance that this will event should actually modify will/roll on panic
  39. var name WillEventActivationType; // if non-none, will events of this type are restricted to only happening once per unit per mission
  40.  
  41. // values for determining how much will to lose
  42. var float WillLossChance; // chance to lose will.
  43. var bool FlatWillLossChance; // if true, will do a straight roll against WillLossChance, if false rolls against WillLossChance - eStat_Will
  44.  
  45. // calculated will loss is the value of the chosen WillLossStat * WillLossStatMultiplier.
  46. // the final will loss is max(<calculated will loss>, MinimumWillLoss)
  47. var WillEventRoll_StatType WillLossStat;
  48. var float WillLossStatMultiplier;
  49. var int MinimumWillLoss;
  50.  
  51. var float MaxWillPercentageLostPerMission; // if > 0, will will only be lost up to this percentage of the unit's max will
  52. var bool CanZeroOutWill; // if false, will always leave at least 1 will remaining
  53.  
  54. // When a unit loses will, a roll will be made against PanicChance. If the roll passes,
  55. // one of the abilities in PanicWeights will be selected.
  56. var float PanicChance; // 0.0-1.0f, will be further reduced by a unit's will
  57. var array<WillEventRollData_PanicWeight> PanicWeights;
  58.  
  59. structdefaultproperties
  60. {
  61. MinimumTiredState = eMentalState_Ready
  62. WillEventActivationChance = 1.0
  63. }
  64. };
  65.  
  66. // text that appears above soldier's head when he loses will from a will event
  67. var localized const string LostWillFlyover;
  68. var localized const string LostWillWorldMessage;
  69. var localized const string DefaultLossSource;
  70. var localized const string PanicTestCaption;
  71. var localized const string PanicResultFlyover;
  72. var localized const string PanicResultWorldMessage;
  73. var localized const string ResistedText;
  74.  
  75. // Percentage of will to lose per bond level when checking WillEventRollStat_BondLevel
  76. var protected const config array<float> BondLevelWillLoss;
  77.  
  78. // the reduction in will loss when this soldier is on a mission with his bondmate
  79. var protected const config float SoldierBondWillReductionMultiplier;
  80.  
  81. // If a soldier does a "normal" panic ability more than once, reroll using the values in this table
  82. var protected const config array<WillEventRollData_PanicWeight> MultiplePanicAltWeights;
  83.  
  84. // It's possible to do multiple rolls per context, so keep track of the running total here.
  85. var privatewrite int RunningWillLossTotal;
  86.  
  87. // the type of will event being conducted
  88. var privatewrite name WillEventType;
  89.  
  90. // It's possible to get multiple panic results from different rolls, so just use the most recent one
  91. var privatewrite name PanicAbilityName;
  92.  
  93. // The unit we are performing the roll on.
  94. var privatewrite XComGameState_Unit SourceUnit;
  95. // The target id our source unit is rolling against.
  96. var privatewrite int TargetUnitID;
  97.  
  98. // Localized friendly name of the roll source. Ex. "Fear of Missing Shots"
  99. var protectedwrite string RollSourceFriendly;
  100. var protectedwrite name RollSource;
  101.  
  102. // If true, will show flyovers and world messages
  103. var protectedwrite bool ShowMessages;
  104.  
  105. // If the target unit is affected by any of these Effects, skip the panic roll
  106. var protected const config array<name> SkipPanicTestWhenAfflictedByEffects;
  107.  
  108. //////////////////////////////////////////////////////////////////////////////////
  109.  
  110. static event bool ShouldPerformWillRoll(const out WillEventRollData RollInfo, XComGameState_Unit AffectedUnit)
  111. {
  112. if( AffectedUnit == none )
  113. {
  114. return false;
  115. }
  116.  
  117. // skip this test on any dead, or evacuated units
  118. if( AffectedUnit.IsDead() ||
  119. AffectedUnit.IsInStasis() ||
  120. AffectedUnit.bRemovedFromPlay )
  121. {
  122. return false;
  123. }
  124.  
  125. // raw random chance to skip this activation
  126. if( `SYNC_FRAND_STATIC() >= RollInfo.WillEventActivationChance )
  127. {
  128. return false;
  129. }
  130.  
  131. return true;
  132. }
  133.  
  134. static function bool ShouldPerformPanicRoll(const out WillEventRollData RollInfo, XComGameState_Unit AffectedUnit)
  135. {
  136. local name TestEffect;
  137.  
  138. if( RollInfo.PanicChance <= 0.0 )
  139. {
  140. return false;
  141. }
  142.  
  143. // restrict rolling on will while panicked
  144. if( !AffectedUnit.IsAbleToAct() )
  145. {
  146. return false;
  147. }
  148.  
  149. // restrict by the units current fatigue state
  150. if( AffectedUnit.GetMentalState() > RollInfo.MinimumTiredState )
  151. {
  152. return false;
  153. }
  154.  
  155. // restrict panic checks to once per turn
  156. if( AffectedUnit.PanicTestsPerformedThisTurn > 0 )
  157. {
  158. return false;
  159. }
  160.  
  161. // restrict if the unit has already rolled on this event type this mission
  162. if( RollInfo.WillEventActivationType != '' && AffectedUnit.WillEventsActivatedThisMission.Find(RollInfo.WillEventActivationType) != INDEX_NONE )
  163. {
  164. return false;
  165. }
  166.  
  167. // Avoid panic tests on units affected by certain status effects
  168. foreach default.SkipPanicTestWhenAfflictedByEffects(TestEffect)
  169. {
  170. if( AffectedUnit.AffectedByEffectNames.Find(TestEffect) != INDEX_NONE )
  171. {
  172. return false;
  173. }
  174. }
  175.  
  176. // skip the roll for panic for any condition that would fail a panic ability
  177. if( class'X2Condition_Panic'.static.StaticCallMeetsCondition(AffectedUnit) != 'AA_Success' )
  178. {
  179. return false;
  180. }
  181.  
  182. return true;
  183. }
  184.  
  185.  
  186. // "SquadmateUnit" needs to be supplied when doing rolls against squadmate rank
  187. event DoWillRoll(WillEventRollData RollInfo, optional XComGameState_Unit SquadmateUnit)
  188. {
  189. if( SquadmateUnit == None )
  190. {
  191. SquadmateUnit = SourceUnit;
  192. }
  193.  
  194. TargetUnitID = SquadmateUnit.ObjectID;
  195.  
  196. WillEventType = RollInfo.WillEventActivationType;
  197.  
  198. CalculateWillRoll(RollInfo, SquadmateUnit, SourceUnit, RollSource, RunningWillLossTotal, PanicAbilityName);
  199.  
  200. if( ShouldPerformPanicRoll(RollInfo, SquadmateUnit) )
  201. {
  202. // also make a panic roll
  203. DoPanicRoll(SquadmateUnit, RunningWillLossTotal, RollInfo.PanicChance, RollInfo.PanicWeights, false, PanicAbilityName);
  204. }
  205. else
  206. {
  207. // never show messages if there was no panic roll
  208. ShowMessages = false;
  209. }
  210. }
  211.  
  212. static function PerformWillRollOnUnitForNewGameState(WillEventRollData RollInfo, XComGameState_Unit InSourceUnit, name InRollSource, XComGameState NewGameState)
  213. {
  214. local int RunningWillLoss;
  215. local name PanicAbility;
  216. local XComGameState_Unit NewSourceUnitState;
  217.  
  218. CalculateWillRoll(RollInfo, None, InSourceUnit, InRollSource, RunningWillLoss, PanicAbility);
  219.  
  220. NewSourceUnitState = XComGameState_Unit(NewGameState.ModifyStateObject(class'XComGameState_Unit', InSourceUnit.ObjectId));
  221. NewSourceUnitState.ModifyCurrentStat(eStat_Will, -RunningWillLoss);
  222. ++NewSourceUnitState.PanicTestsPerformedThisTurn;
  223.  
  224. if( RollInfo.WillEventActivationType != '' && NewSourceUnitState.WillEventsActivatedThisMission.Find(RollInfo.WillEventActivationType) == INDEX_NONE )
  225. {
  226. NewSourceUnitState.WillEventsActivatedThisMission.AddItem(RollInfo.WillEventActivationType);
  227. }
  228. }
  229.  
  230. static function CalculateWillRoll(WillEventRollData RollInfo, XComGameState_Unit SquadmateUnit, XComGameState_Unit InSourceUnit, name InRollSource, out int InRunningWillLossTotal, out name InPanicAbilityName)
  231. {
  232. local XComGameStateHistory History;
  233. local XComGameState_Unit StartOfMissionUnit;
  234. local bool ShouldLoseWill;
  235. local float CalculatedWillLoss;
  236. local int WillLossOverage;
  237. local float WillLossRemainder;
  238. local int CurrentWill; // current will, including the running loss total
  239. local int MaximumWillLossPerMission;
  240. local int MinimumAllowedWill;
  241. local SoldierBond BondData;
  242. local bool bBondmateOnMission;
  243. local StateObjectReference BondmateRef;
  244. local XComGameState_BattleData BattleDataState;
  245. local X2SitRepEffect_ModifyWillPenalties SitRepEffect;
  246. local float SitRepWillLossScalar;
  247.  
  248. if( InSourceUnit.IsDead()
  249. || InSourceUnit.IsIncapacitated()
  250. || InSourceUnit.IsMindControlled())
  251. {
  252. return;
  253. }
  254.  
  255. if( InSourceUnit.UsesWillSystem())
  256. {
  257. if(RollInfo.FlatWillLossChance)
  258. {
  259. ShouldLoseWill = class'Engine'.static.SyncFRand("DoWillRoll") < RollInfo.WillLossChance;
  260. }
  261. else
  262. {
  263. ShouldLoseWill = class'Engine'.static.SyncFRand("DoWillRoll") < RollInfo.WillLossChance - (InSourceUnit.GetCurrentStat(eStat_Will) * 0.01f);
  264. }
  265. }
  266.  
  267. if(ShouldLoseWill)
  268. {
  269. bBondmateOnMission = InSourceUnit.HasSoldierBond(BondmateRef, BondData) && `XCOMHQ.IsUnitInSquad(BondmateRef);
  270.  
  271. // determine how much will we want to lose
  272. switch(RollInfo.WillLossStat)
  273. {
  274. case WillEventRollStat_None:
  275. CalculatedWillLoss = 0;
  276. break;
  277. case WillEventRollStat_PercentageHealthLost:
  278. CalculatedWillLoss = InSourceUnit.GetCurrentStat(eStat_HP) / InSourceUnit.GetMaxStat(eStat_HP);
  279. break;
  280. case WillEventRollStat_SquadmateRank:
  281. assert(SquadmateUnit != none);
  282. CalculatedWillLoss = SquadmateUnit.GetSoldierRank();
  283. break;
  284. case WillEventRollStat_MaxWill:
  285. CalculatedWillLoss = InSourceUnit.GetMaxStat(eStat_Will);
  286. break;
  287. case WillEventRollStat_BondLevel:
  288. if( bBondmateOnMission )
  289. {
  290. if(BondData.BondLevel < 0 || BondData.BondLevel > default.BondLevelWillLoss.Length)
  291. {
  292. `Redscreen("DoWillRoll(): " $ InSourceUnit.GetFullName() $ "has an unexpected bond level " $ BondData.BondLevel);
  293. BondData.BondLevel = 0;
  294. }
  295. CalculatedWillLoss = InSourceUnit.GetMaxStat(eStat_Will) * default.BondLevelWillLoss[BondData.BondLevel];
  296. }
  297. break;
  298. default:
  299. `assert(false);
  300. }
  301.  
  302. // Gather sitrep granted abilities
  303. BattleDataState = XComGameState_BattleData(`XCOMHISTORY.GetSingleGameStateObjectForClass(class'XComGameState_BattleData'));
  304. SitRepWillLossScalar = 1.0f;
  305. if (BattleDataState != none)
  306. {
  307. foreach class'X2SitreptemplateManager'.static.IterateEffects(class'X2SitRepEffect_ModifyWillPenalties', SitRepEffect, BattleDataState.ActiveSitReps)
  308. {
  309. if ((SitRepEffect.WillEventNames.Length == 0) || (SitRepEffect.WillEventNames.Find(InRollSource) != INDEX_NONE))
  310. {
  311. SitRepWillLossScalar *= SitRepEffect.ModifyScalar;
  312. }
  313. }
  314. }
  315.  
  316. CalculatedWillLoss = (CalculatedWillLoss * RollInfo.WillLossStatMultiplier * SitRepWillLossScalar);
  317.  
  318. if( bBondmateOnMission )
  319. {
  320. CalculatedWillLoss *= default.SoldierBondWillReductionMultiplier;
  321. }
  322.  
  323. // if we have a remainder, roll on it for an extra integer point
  324. WillLossRemainder = CalculatedWillLoss % 1.0f;
  325. if(class'Engine'.static.SyncFRand("DoWillRoll") < WillLossRemainder)
  326. {
  327. CalculatedWillLoss += 1;
  328. }
  329.  
  330. // make sure we are taking at least the minimum amount
  331. CalculatedWillLoss = max(int(CalculatedWillLoss), RollInfo.MinimumWillLoss);
  332.  
  333. // since the history hasn't been updated yet, add in the will loss from previous rolls
  334. CurrentWill = InSourceUnit.GetCurrentStat(eStat_Will) - InRunningWillLossTotal;
  335.  
  336. // and determine if we have any lower floor on how much will we are allowed to lose in the mission.
  337. // by default, we can go all the way to zero
  338. MinimumAllowedWill = 0;
  339. if(!RollInfo.CanZeroOutWill)
  340. {
  341. // don't allow dropping below 1
  342. MinimumAllowedWill = 1;
  343. }
  344.  
  345. if(RollInfo.MaxWillPercentageLostPerMission > 0)
  346. {
  347. // get the unit's will at the start of the mission
  348. History = `XCOMHISTORY;
  349. StartOfMissionUnit = XComGameState_Unit(History.GetGameStateForObjectID(InSourceUnit.ObjectID,, History.FindStartStateIndex()));
  350. MaximumWillLossPerMission = RollInfo.MaxWillPercentageLostPerMission * InSourceUnit.GetMaxStat(eStat_Will);
  351. MinimumAllowedWill = max(MinimumAllowedWill, StartOfMissionUnit.GetCurrentStat(eStat_Will) - MaximumWillLossPerMission);
  352. }
  353.  
  354. // now that we know our lower floor, make sure we won't drop below the minimum
  355. WillLossOverage = CurrentWill - (CalculatedWillLoss + MinimumAllowedWill);
  356. CalculatedWillLoss -= Max(0, -WillLossOverage);
  357.  
  358. // add in the loss for this roll to the running total
  359. InRunningWillLossTotal += CalculatedWillLoss;
  360. }
  361. }
  362.  
  363. static function DoPanicRoll(XComGameState_Unit InSourceUnit, int InRunningWillLossTotal, float PanicChance, array<WillEventRollData_PanicWeight> PanicWeights, bool Reroll, out name InPanicAbilityName)
  364. {
  365. local int CurrentWill;
  366. local float PanicRoll;
  367. local WillEventRollData_PanicWeight PanicWeight;
  368. local float TotalPanicWeight;
  369.  
  370. CurrentWill = InSourceUnit.GetCurrentStat(eStat_Will) - InRunningWillLossTotal;
  371.  
  372. PanicRoll = `SYNC_FRAND_STATIC("DoWillRoll");
  373. if((PanicRoll < (PanicChance - (CurrentWill * 0.01)))
  374. || CurrentWill <= 0
  375. || (`CHEATMGR != none && `CHEATMGR.bAlwaysPanic))
  376. {
  377. // do a weighted pick from the available choices. Add up the total weight of all options,
  378. // do a random roll from [0..TotalWeight], and then start adding up the weights again.
  379. // when the total goes over the random number, we have our choice.
  380. foreach PanicWeights(PanicWeight)
  381. {
  382. TotalPanicWeight += PanicWeight.Weight;
  383. }
  384.  
  385. PanicRoll = `SYNC_FRAND_STATIC("DoWillRoll") * TotalPanicWeight;
  386.  
  387. TotalPanicWeight = 0.0f;
  388. foreach PanicWeights(PanicWeight)
  389. {
  390. TotalPanicWeight += PanicWeight.Weight;
  391. if(TotalPanicWeight >= PanicRoll)
  392. {
  393. InPanicAbilityName = PanicWeight.PanicAbilityName;
  394.  
  395. // To favor the new panic behaviors, if we roll a normal panic and this unit has already
  396. // done a normal panic on this mission, reroll (but only once)
  397. if(!Reroll
  398. && InPanicAbilityName == 'Panicked'
  399. && UnitHasAlreadyUsedNormalPanicThisMission(InSourceUnit))
  400. {
  401. DoPanicRoll(InSourceUnit, InRunningWillLossTotal, PanicChance, default.MultiplePanicAltWeights, true, InPanicAbilityName);
  402. }
  403.  
  404. break;
  405. }
  406. }
  407. }
  408. }
  409.  
  410. // Does what it says, nativized since it can require a lot of iteration late into missions.
  411. static native function bool UnitHasAlreadyUsedNormalPanicThisMission(XComGameState_Unit InSourceUnit);
  412.  
  413. function bool Validate(optional EInterruptionStatus InInterruptionStatus)
  414. {
  415. return true;
  416. }
  417.  
  418. function XComGameState ContextBuildGameState()
  419. {
  420. local XComGameState NewGameState;
  421. local XComGameState_Unit UnitState;
  422. local X2EventManager Manager;
  423. local Object SelfObj;
  424.  
  425. // Add the units to the new game state
  426. NewGameState = `XCOMHISTORY.CreateNewGameState(true, self);
  427.  
  428. UnitState = XComGameState_Unit(NewGameState.ModifyStateObject(SourceUnit.Class, SourceUnit.ObjectId));
  429. UnitState.ModifyCurrentStat(eStat_Will, -RunningWillLossTotal);
  430.  
  431. if( WillEventType != '' && UnitState.WillEventsActivatedThisMission.Find(WillEventType) == INDEX_NONE )
  432. {
  433. UnitState.WillEventsActivatedThisMission.AddItem(WillEventType);
  434. }
  435.  
  436. // trigger an event to do our panic behavior if one was selected. We need to wait for this context
  437. // to complete before we can do the panic, and an event will serve that purpose just fine
  438. if(PanicAbilityName != '')
  439. {
  440. SelfObj = self;
  441. Manager = `XEVENTMGR;
  442. Manager.RegisterForEvent(SelfObj, 'DoPanicAbility', OnDoPanicAbility, ELD_OnStateSubmitted);
  443. Manager.TriggerEvent('DoPanicAbility', UnitState, self, NewGameState);
  444. }
  445.  
  446. NewGameState.GetContext().SetAssociatedPlayTiming(SPT_AfterSequential);
  447.  
  448. return NewGameState;
  449. }
  450.  
  451. function EventListenerReturn OnDoPanicAbility(Object EventData, Object EventSource, XComGameState GameState, Name Event, Object CallbackData)
  452. {
  453. local XComGameStateHistory History;
  454. local GameRulesCache_Unit UnitCache;
  455. local XComGameState_Unit UnitState;
  456. local XComGameState_Ability AbilityState;
  457. local int i;
  458.  
  459. History = `XCOMHISTORY;
  460.  
  461. UnitState = XComGameState_Unit(EventData);
  462. if(EventSource != self)
  463. {
  464. return ELR_NoInterrupt;
  465. }
  466.  
  467. // find the specified panic ability and activate it
  468. if (`TACTICALRULES.GetGameRulesCache_Unit(UnitState.GetReference(), UnitCache))
  469. {
  470. for (i = 0; i < UnitCache.AvailableActions.Length; ++i)
  471. {
  472. AbilityState = XComGameState_Ability(History.GetGameStateForObjectID(UnitCache.AvailableActions[i].AbilityObjectRef.ObjectID));
  473. if (AbilityState.GetMyTemplateName() == PanicAbilityName)
  474. {
  475. if (UnitCache.AvailableActions[i].AvailableCode == 'AA_Success' && UnitCache.AvailableActions[i].AvailableTargets.Length > 0)
  476. {
  477. class'XComGameStateContext_Ability'.static.ActivateAbility(UnitCache.AvailableActions[i], 0, , , , , , , SPT_AfterSequential);
  478. }
  479. break;
  480. }
  481. }
  482. }
  483.  
  484. `XEVENTMGR.UnRegisterFromEvent(EventSource, 'DoPanicAbility');
  485. return ELR_NoInterrupt;
  486. }
  487.  
  488. protected function ContextBuildVisualization()
  489. {
  490. local VisualizationActionMetadata ActionMetadata, EmptyMetadata;
  491. local X2Action_PlaySoundAndFlyover FlyoverAction;
  492. //local X2Action_PlayMessageBanner WorldMessageAction;
  493. local XComGameState_Unit UnitState;
  494. local XGParamTag Tag;
  495. local X2Action_RevealArea RevealAreaAction;
  496. local XComWorldData WorldData;
  497. local TTile TileLocation;
  498. local Vector WorldLocation;
  499. local X2Action_CameraLookAt LookAtAction;
  500. local X2Action_StartStopSound SoundAction;
  501. local X2Action_UpdateUI UIUpdateAction;
  502. local X2AbilityTemplate AbilityTemplate;
  503. local X2Action_Delay DelayAction;
  504. local X2Action_CentralBanner BannerAction;
  505.  
  506. // add a flyover track for every unit in the game state. This timing of this is too late to look good
  507. // but since Ryan is tearing it all out there is no reason to augment the visualization system to allow
  508. // it to look pretty
  509. if(ShowMessages)
  510. {
  511. WorldData = `XWORLD;
  512.  
  513. Tag = XGParamTag(`XEXPANDCONTEXT.FindTag("XGParam"));
  514. Tag.StrValue0 = RollSourceFriendly;
  515. Tag.IntValue0 = RunningWillLossTotal;
  516.  
  517. if( PanicAbilityName == '' )
  518. {
  519. Tag.StrValue2 = ResistedText;
  520. }
  521. else
  522. {
  523. AbilityTemplate = class'XComGameState_Ability'.static.GetMyTemplateManager().FindAbilityTemplate(PanicAbilityName);
  524. Tag.StrValue2 = AbilityTemplate.LocFriendlyName;
  525. }
  526.  
  527. // clear unnecessary HUD components
  528. UIUpdateAction = X2Action_UpdateUI(class'X2Action_UpdateUI'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  529. UIUpdateAction.UpdateType = EUIUT_SetHUDVisibility;
  530. UIUpdateAction.DesiredHUDVisibility.bMessageBanner = true;
  531.  
  532. foreach AssociatedState.IterateByClassType(class'XComGameState_Unit', UnitState)
  533. {
  534. UnitState.GetKeystoneVisibilityLocation(TileLocation);
  535. WorldLocation = WorldData.GetPositionFromTileCoordinates(TileLocation);
  536.  
  537. Tag.StrValue1 = UnitState.GetFullName();
  538.  
  539. ActionMetadata = EmptyMetadata;
  540. ActionMetadata.StateObject_OldState = UnitState.GetPreviousVersion();
  541. ActionMetadata.StateObject_NewState = UnitState;
  542.  
  543. Tag.StrValue0 = RollSourceFriendly;
  544.  
  545. // new visualization flow:
  546. // for each affected unit:
  547. // 1a) Clear FOW around unit
  548. RevealAreaAction = X2Action_RevealArea(class'X2Action_RevealArea'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  549. RevealAreaAction.ScanningRadius = class'XComWorldData'.const.WORLD_StepSize * 5.0f;
  550. RevealAreaAction.TargetLocation = WorldLocation;
  551. RevealAreaAction.bDestroyViewer = false;
  552. RevealAreaAction.AssociatedObjectID = UnitState.ObjectID;
  553.  
  554. // 1b) Focus camera on unit (wait until centered)
  555. LookAtAction = X2Action_CameraLookAt(class'X2Action_CameraLookAt'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  556. LookAtAction.LookAtLocation = WorldLocation;
  557. LookAtAction.BlockUntilActorOnScreen = true;
  558. LookAtAction.LookAtDuration = 10.0f; // longer than we need - camera will be removed by tag below
  559. LookAtAction.TargetZoomAfterArrival = -0.7f;
  560. LookAtAction.CameraTag = 'WillTestCamera';
  561. LookAtAction.bRemoveTaggedCamera = false;
  562.  
  563. // 2a) Raise Special Event overlay "Breaking Point: <RollSource>"
  564. BannerAction = X2Action_CentralBanner(class'X2Action_CentralBanner'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  565. BannerAction.BannerText = `XEXPAND.ExpandString(PanicTestCaption);
  566. BannerAction.BannerState = eUIState_Normal;
  567.  
  568. // 2b) Play will loss update on UnitFlag will bar
  569. UIUpdateAction = X2Action_UpdateUI(class'X2Action_UpdateUI'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  570. UIUpdateAction.SpecificID = UnitState.ObjectID;
  571. UIUpdateAction.UpdateType = EUIUT_UnitFlag_Health;
  572.  
  573. // 2c) Trigger Panic Event buildup audio FX
  574. SoundAction = X2Action_StartStopSound(class'X2Action_StartStopSound'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  575. SoundAction.Sound = new class'SoundCue';
  576. SoundAction.Sound.AkEventOverride = AkEvent'XPACK_SoundTacticalUI.Panic_Check_Start';
  577. SoundAction.bIsPositional = false;
  578. SoundAction.vWorldPosition = WorldLocation;
  579. SoundAction.WaitForCompletion = true;
  580.  
  581. // 3a) Wait 0.5s - or until SoundAction is complete
  582. DelayAction = X2Action_Delay(class'X2Action_Delay'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  583. DelayAction.Duration = 2.5;
  584. DelayAction.bIgnoreZipMode = true;
  585.  
  586. // 4a) Update Special Event overlay to show panic results
  587. BannerAction = X2Action_CentralBanner(class'X2Action_CentralBanner'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  588. BannerAction.BannerText = `XEXPAND.ExpandString(PanicResultFlyover);
  589. if( PanicAbilityName == '' )
  590. {
  591. BannerAction.BannerState = eUIState_Good;
  592. }
  593. else
  594. {
  595. BannerAction.BannerState = eUIState_Bad;
  596. }
  597.  
  598. // 4b) Trigger Result flyover (ex. "Resisted", "Panicked")
  599. // Update 4/20 - only trigger if the result was "resisted"
  600. if( PanicAbilityName == '' )
  601. {
  602. FlyoverAction = X2Action_PlaySoundAndFlyover(class'X2Action_PlaySoundAndFlyover'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  603. FlyoverAction.SetSoundAndFlyOverParameters(none, `XEXPAND.ExpandString(PanicResultFlyover), '', eColor_Good);
  604. }
  605.  
  606. // 4c) Trigger result audio FX
  607. SoundAction = X2Action_StartStopSound(class'X2Action_StartStopSound'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  608. SoundAction.Sound = new class'SoundCue';
  609. if( PanicAbilityName == '' )
  610. {
  611. // good result sound
  612. SoundAction.Sound.AkEventOverride = AkEvent'SoundTacticalUI.TacticalUI_UnitFlagPositive';
  613. }
  614. else
  615. {
  616. // bad result sound
  617. SoundAction.Sound.AkEventOverride = AkEvent'SoundTacticalUI.TacticalUI_UnitFlagWarning';
  618. }
  619. SoundAction.bIsPositional = false;
  620. SoundAction.vWorldPosition = WorldLocation;
  621. SoundAction.WaitForCompletion = false;
  622.  
  623. // 5a) complete panic behavior visualization (if applicable)
  624. // - for now, will just rely on the panic processing to complete the behavior visualization
  625.  
  626. // 5a) Wait 1.5s
  627. DelayAction = X2Action_Delay(class'X2Action_Delay'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  628. DelayAction.bIgnoreZipMode = true;
  629. if( PanicAbilityName == '' )
  630. {
  631. // if the panic was resisted, there will not be a panic visualization following this action,
  632. // so we need to give the user more time to enjoy the "resisted" flyover
  633. DelayAction.Duration = 3.0;
  634. }
  635. else
  636. {
  637. DelayAction.Duration = 1.5;
  638. }
  639.  
  640. // 6a) Lower Special Event overlay
  641. BannerAction = X2Action_CentralBanner(class'X2Action_CentralBanner'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  642. BannerAction.BannerText = "";
  643.  
  644. // 6b) restore FOW
  645. RevealAreaAction = X2Action_RevealArea(class'X2Action_RevealArea'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  646. RevealAreaAction.bDestroyViewer = true;
  647. RevealAreaAction.AssociatedObjectID = UnitState.ObjectID;
  648.  
  649. // 6c) release camera
  650. LookAtAction = X2Action_CameraLookAt(class'X2Action_CameraLookAt'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  651. LookAtAction.CameraTag = 'WillTestCamera';
  652. LookAtAction.bRemoveTaggedCamera = true;
  653.  
  654. // 6d) Play world message explaining what just happened
  655. // Update 4/20 - no banner needed when resisting; the panic state will handle the banner when the will check is failed
  656. //WorldMessageAction = X2Action_PlayMessageBanner(class'X2Action_PlayMessageBanner'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  657. //WorldMessageAction.AddMessageBanner(class'UIEventNoticesTactical'.default.PanicCheckTitle,
  658. // /*class'UIUtilities_Image'.const.UnitStatus_Panicked*/,
  659. // UnitState.GetName(eNameType_RankFull),
  660. // `XEXPAND.ExpandString(PanicResultWorldMessage),
  661. // (PanicAbilityName == '') ? eUIState_Good : eUIState_Bad);
  662. }
  663.  
  664. // restore HUD components to previous vis state
  665. UIUpdateAction = X2Action_UpdateUI(class'X2Action_UpdateUI'.static.AddToVisualizationTree(ActionMetadata, self, false, ActionMetadata.LastActionAdded));
  666. UIUpdateAction.UpdateType = EUIUT_RestoreHUDVisibility;
  667. }
  668. }
  669.  
  670. static event XComGameStateContext_WillRoll CreateWillRollContext(XComGameState_Unit InSourceUnit, name InRollSource, optional string InRollSourceFriendly, optional bool InShowMessages = true)
  671. {
  672. local XComGameStateContext_WillRoll RollContext;
  673.  
  674. `assert(InSourceUnit != none);
  675. RollContext = XComGameStateContext_WillRoll(CreateXComGameStateContext());
  676. RollContext.SourceUnit = InSourceUnit;
  677. RollContext.RollSource = InRollSource;
  678. RollContext.RollSourceFriendly = InRollSourceFriendly == "" ? default.DefaultLossSource : InRollSourceFriendly;
  679. RollContext.ShowMessages = InShowMessages;
  680. return RollContext;
  681. }
  682.  
  683. event Submit()
  684. {
  685. local X2TacticalGameRuleset Rules;
  686.  
  687. if(SourceUnit.IsDead() || SourceUnit.IsIncapacitated() || SourceUnit.IsMindControlled())
  688. {
  689. return;
  690. }
  691.  
  692. if(RunningWillLossTotal > 0 || PanicAbilityName != '')
  693. {
  694. Rules = `TACTICALRULES;
  695. if(Rules == none)
  696. {
  697. `Redscreen("Attempting to submit a will roll outside of tactical.");
  698. return;
  699. }
  700.  
  701. if(!Rules.SubmitGameStateContext(self))
  702. {
  703. `Redscreen("Unable to submit will roll context.");
  704. }
  705. }
  706. }
  707.  
  708. function string SummaryString()
  709. {
  710. return "Will Roll: " $ SourceUnit.GetFullName() $ " lost " $ RunningWillLossTotal $ " Will";
  711. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement