long-war-2 / lwotc

Port of Long War 2 to XCOM 2's War of the Chosen expansion
342 stars 89 forks source link

Switch to WOTC timer for mission maps #22

Closed pledbrook closed 5 years ago

pledbrook commented 5 years ago

LW2 coded its own mission timer into various maps, but this should no longer be necessary with WOTC's BasicMissionTimer Kismet sequence (or set of sequences). It has an integer variable (Timer.LengthDelta, default: 0) that gets added the to base mission time (which comes from config) and a float variable (Timer.DeltaMultiplier, default: 1.0) that is multiplied against the result.

It should be possible to set appropriate base times for the missions in config (see DefaultMission.ini - under [RescueVIP] for example) and then inject appropriate modifiers based on the size of the map (see X2SitRepEffect_ModifyKismetVariable for an example of injecting Kismet variables from UnrealScript).

Note that we also need to take into account officer abilities that adjust mission timers. This are currently coded against the LW2 mission timer rather than the WOTC one. See X2Effect_SuspendMissionTimer for an example of interacting with the mission timer via abilities/effects.

pledbrook commented 5 years ago

@tracktwo The SitRep template uses a private native method to modify Kismet variables. And I don't think I can use a SitRep to solve the problem of adjusting timers to map size because they apply (and are displayed) when the mission is created in the strategy layer, not when the tactical mission is created.

Are there any other solutions to injecting or modifying Kismet variables from UnrealScript? If not, I guess I'll have to copy BasicMissionTimer and insert a custom SeqAct hook to adjust the timer values.

tracktwo commented 5 years ago

I only briefly looked at the new timer sequence and don't have the SDK handy right now, but I thought the actual timer variable now lived in a game state object, and kismet just manipulated that rather than keeping a per-mission kismet variable that 'owns' the timer. If that's the case you can alter the timer right from uscript without needing to mess with kismet at all (modulo any UI refresh issues).

pledbrook commented 5 years ago

Timer.TurnsRemaining and Timer.BaseLength both appear to be defined in the mission Kismet. That Kismet also pulls some values from config, but only the base timer length is relevant to us. That's my understanding of what I see, anyway. I could very well be missing something.

tracktwo commented 5 years ago

Ok, so I was wrong - the mission timer is still owned by kismet. The game state object only has control over suspending the timer, not adjusting the mission timer count, and the sitrep stuff happens by tweaking the timer rules according to specific hardcoded sets of variables. So it's still not terribly mod-friendly, unfortunately.

There are ways to modify the kismet variables from uscript, but they are kind of ugly. If all you need to do is increase or decrease the timer by a particular number of turns during a tactical mission you can probably do it by triggering the "GiveMeOneMoreTurn" and "ReduceTimerOneTurn" remote events. For an example of this see X2HackRewards.ApplySecureDoorUnlock and the corresponding event sequence in the compound rescue mission. That shows how uscript can call into pre-defined kismet sequences, but it's mostly used for triggering cinematics and things like that. Using it to alter kismet logic is a bit unusual, but it should work. Doing that at mission start to be able to influence the timer based on distance to objective might be tricky to get the order of operations right, but it might be sufficient to be able to implement the officer intervention ability.

The alternative would still be to build a clone of UMS_BasicMissionTimer that includes the extensions you need.

pledbrook commented 5 years ago

Ah well, I'm at least glad I was interpreting the Kismet correctly. Means I'm learning something. Thanks for verifying.

I think I may just bite the bullet and clone UMS_BasicMissionTimer for the flexility and insert the "Initialize Mission Timer" sequence action from the existing LW maps. That's also more likely to benefit other mods that may want to borrow the functionality. Not sure I need to add "Get Mission Timer" as well, but I'll double check.

tracktwo commented 5 years ago

The LW2 sequence action basically just changed the turn start timer sequence so that instead of reading and manipulating the Mission.Timer kismet variable directly, it first loaded the kismet variable from the game state object. Once loaded it'd run the sequence as normal, including decrement, etc, and finally the change on the other end was to push the modified timer variable back into the game state. In other words, the kismet timer variable was transient, the game state was the source of truth for the timer value. That's why it needed both a "get mission timer" and a "set mission timer". The intent was to try to minimize the number of kismet changes needed since they were copied in each mission rather than in a common subsequence.

pledbrook commented 5 years ago

🤔 The only value I see in making XComGameState_UITimer the source of truth is so that its value can be modified outside of the Kismet sequences. But does that not open up the possibility for Kismet sequences to break? After all, they're designed around the idea that Timer.TurnsRemaining is the source of truth and XComGameState_UITimer is just for the visuals.

pledbrook commented 5 years ago

:rofl: Well, my argument was just shot out of the water. Apparently the Kismet queries XComGameState_UITimer to find out whether it's suspended or not (because scripts suspend it when the Chosen appear on a map). So I guess there's no reason not to insert a Get Mission Timer.

tracktwo commented 5 years ago

I suppose there is the possibility for kismet sequences to break, but in practice we didn't have any problems with this. In LW2 we "knew" that the only use of the timer kismet var was in the turn change sequence, so as long as we were careful to refresh it properly there everything works. The risk would be if some other kismet sequence for some mission reads Timer.TurnsRemaining without pulling it from the game state first, and some game code modified the timer since the last time the turn changed naturally. In LW2 this only ever happened with the officer intervention ability, but no sequences ever did this so it didn't matter in practice. It's possible that mods could introduce new mission types that use the timer for other purposes outside of turn changes, but those missions also would just be using the vanilla turn timer logic so that aspect would work, but no other LW2 behavior would on those mission types. In particular intervention wouldn't do anything.

The LW2 timer changes were made cause we wanted the following:

1 To be able to adjust the timer with tactical abilities (Intervention) 2 To be able to easily customize the mission timer values by difficulty or other strategy level effects. AKA when JL was balancing he didn't want to have to go into all the mission maps and change kismet, he could just tweak an .ini setting until things were ok. In vanilla xcom2 all the timers were just hardcoded in the kismet timer variable's definition in each individual map.

(2) can now be handled directly in wotc by the same mechanism sitreps use, but (1) doesn't have a direct solution. Your (3) be able to adjust the timer start value based on the tactical map wasn't something we had in lw2, but would have worked with the lw2 timer system (edit: filthy lies from my bad memory)

pledbrook commented 5 years ago

? I'm confused. SeqAct_InitializeMissionTimer already adjusts the timer based on size of tactical map. That's not something I've done.

tracktwo commented 5 years ago

Now I'm confused. I thought that was what you were trying to do? Base xcom2 didn't have that mechanic, I don't think.

pledbrook commented 5 years ago

So, Long War 2 1.5 adjusts the timer based on map size. I'm trying to migrate that behaviour to WOTC, which doesn't have that mechanic (just like original XCOM 2). Basically, I'm only confused by your statement that the mechanic wasn't in LW2.

tracktwo commented 5 years ago

Oh, hah. I stand corrected then. Either I didn't implement that or I did and then promptly forgot about it. Either is likely :)

Sorry for the confusion!

pledbrook commented 5 years ago

See this commit