Skip to content

Commit 4158b55

Browse files
committed
feat: allow pre-processing of some tools
this resolves issues where some basic tools (like use or cmd) would not be processed correctly due to being executed late into user cmd processing (which is required for other tools because of altticks handling)
1 parent 47442a5 commit 4158b55

25 files changed

+194
-119
lines changed

src/Features/Tas/TasController.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,8 @@ void TasController::SetButtonState(TasControllerInput i, bool state) {
110110
std::chrono::time_point<std::chrono::high_resolution_clock> g_lastControllerMove;
111111

112112
void TasController::ControllerMove(int nSlot, float flFrametime, CUserCmd *cmd) {
113-
// ControllerMove is executed several times for one tick, idk why,
114-
// but only once with tick_count bigger than 0. Working only
115-
// on these seems to work fine, so I assume these are correct.
113+
// ControllerMove is executed several times for one tick. Most of them
114+
// are called from ExtraMouseSamples with 0 tick count. We want to filter them out.
116115
if (cmd->tick_count == 0) return;
117116

118117
// doing some debugs to test the behaviour of the real controller
@@ -141,7 +140,7 @@ void TasController::ControllerMove(int nSlot, float flFrametime, CUserCmd *cmd)
141140

142141
//console->Print("TasController::ControllerMove (%d, ", cmd->tick_count);
143142

144-
tasPlayer->FetchInputs(nSlot, this);
143+
tasPlayer->FetchInputs(nSlot, this, cmd);
145144

146145
//TAS is now controlling inputs. Reset everything we can.
147146
cmd->forwardmove = 0;

src/Features/Tas/TasParser.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22
#include "TasScript.hpp"
33

4-
#define MAX_SCRIPT_VERSION 8
4+
#define MAX_SCRIPT_VERSION 9
55

66
#include <iostream>
77
#include <string>

src/Features/Tas/TasPlayer.cpp

Lines changed: 123 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ void TasPlayer::Activate(TasPlaybackInfo info) {
154154
for (int slot = 0; slot < 2; ++slot) {
155155
playbackInfo.slots[slot].ClearGeneratedContent();
156156

157-
currentInputFramebulkIndex[slot] = 0;
158-
currentToolsFramebulkIndex[slot] = 0;
157+
currentRequestRawFramebulkIndex[slot] = 0;
158+
159159
}
160160

161161
active = true;
@@ -350,6 +350,28 @@ TasFramebulk TasPlayer::GetRawFramebulkAt(int slot, int tick, unsigned& cachedIn
350350
return playbackInfo.slots[slot].framebulks[cachedIndex];
351351
}
352352

353+
TasFramebulk &TasPlayer::RequestProcessedFramebulkAt(int slot, int tick) {
354+
auto &processed = playbackInfo.slots[slot].processedFramebulks;
355+
auto processedCount = processed.size();
356+
357+
if (processedCount == 0 || processed.back().tick < tick) {
358+
TasFramebulk fb = GetRawFramebulkAt(slot, tick, currentRequestRawFramebulkIndex[slot]);
359+
processed.push_back(fb);
360+
return processed.back();
361+
}
362+
363+
// if it already exists, it should be near the end, as we usually request the newest ones
364+
for (int index = processedCount - 1; index >= 0; --index) {
365+
if (processed[index].tick == tick) {
366+
return processed[index];
367+
} else if (processed[index].tick < tick) {
368+
break;
369+
}
370+
}
371+
372+
console->Warning("TAS processed framebulk for tick %d not found! This should not happen!\n", tick);
373+
}
374+
353375
TasPlayerInfo TasPlayer::GetPlayerInfo(int slot, void *player, CUserCmd *cmd, bool clientside) {
354376
TasPlayerInfo pi;
355377

@@ -540,7 +562,7 @@ void TasPlayer::SaveProcessedFramebulks() {
540562
we assume the response time for our "virtual controller" to be
541563
non-existing and just let it parse inputs corresponding to given tick.
542564
*/
543-
void TasPlayer::FetchInputs(int slot, TasController *controller) {
565+
void TasPlayer::FetchInputs(int slot, TasController *controller, CUserCmd* cmd) {
544566
// Slight hack! Input fetching (including SteamControllerMove) is
545567
// called through _Host_RunFrame_Input, which is called *before*
546568
// GameFrame (that being called via _Host_RunFrame_Server). Therefore,
@@ -549,36 +571,23 @@ void TasPlayer::FetchInputs(int slot, TasController *controller) {
549571
// said than done since the input fetching code is only run when the
550572
// client is connected, so to match the behaviour we'd probably need
551573
// to actually hook at _Host_RunFrame_Input or CL_Move.
552-
int tick = currentTick + 1;
553-
554-
TasFramebulk fb = GetRawFramebulkAt(slot, tick, currentInputFramebulkIndex[slot]);
574+
int tasTick = currentTick + 1;
555575

556-
int fbTick = fb.tick;
576+
auto player = server->GetPlayer(slot + 1);
557577

558-
if (sar_tas_debug.GetInt() > 0 && fbTick == tick) {
559-
console->Print("%s\n", fb.ToString().c_str());
578+
if (tasTick == 1) {
579+
SamplePreProcessedFramebulk(slot, 0, player, cmd);
560580
}
561581

582+
TasFramebulk fb = SamplePreProcessedFramebulk(slot, tasTick, player, cmd);
583+
562584
controller->SetViewAnalog(fb.viewAnalog.x, fb.viewAnalog.y);
563585
controller->SetMoveAnalog(fb.moveAnalog.x, fb.moveAnalog.y);
564586
for (int i = 0; i < TAS_CONTROLLER_INPUT_COUNT; i++) {
565587
controller->SetButtonState((TasControllerInput)i, fb.buttonStates[i]);
566588
}
567-
568-
if (tick == 1) {
569-
// on tick 1, we'll run the commands from the bulk at tick 0 because
570-
// of the annoying off-by-one thing explained above
571-
TasFramebulk fb0 = GetRawFramebulkAt(slot, 0);
572-
for (std::string cmd : fb0.commands) {
573-
controller->AddCommandToQueue(cmd);
574-
}
575-
}
576-
577-
// add commands only for tick when framebulk is placed. Don't preserve it to other ticks.
578-
if (tick == fbTick) {
579-
for (std::string cmd : fb.commands) {
580-
controller->AddCommandToQueue(cmd);
581-
}
589+
for (std::string cmd : fb.commands) {
590+
controller->AddCommandToQueue(cmd);
582591
}
583592
}
584593

@@ -591,6 +600,52 @@ static bool IsTaunting(ClientEnt *player) {
591600
return false;
592601
}
593602

603+
TasFramebulk TasPlayer::SamplePreProcessedFramebulk(int slot, int tasTick, void *player, CUserCmd *cmd) {
604+
605+
auto pInfo = GetPlayerInfo(slot, server->GetPlayer(slot + 1), cmd);
606+
TasFramebulk& fb = RequestProcessedFramebulkAt(slot, tasTick);
607+
608+
auto fbTick = fb.tick;
609+
fb.tick = tasTick;
610+
bool framebulkUpdated = (fbTick == tasTick);
611+
612+
if (sar_tas_debug.GetInt() > 0 && framebulkUpdated) {
613+
console->Print("(TAS: rawtick) %s\n", fb.ToString().c_str());
614+
}
615+
616+
if (tasTick == 0 || !framebulkUpdated) {
617+
std::vector<std::string> emptyCommands;
618+
fb.commands = emptyCommands;
619+
}
620+
621+
if (!framebulkUpdated) {
622+
std::vector<TasToolCommand> emptyToolCmds;
623+
fb.toolCmds = emptyToolCmds;
624+
}
625+
626+
if (tasTick == 1) {
627+
// on tick 1, we'll run the commands from the bulk at tick 0 because
628+
// of the annoying off-by-one thing explained in FetchInputs
629+
TasFramebulk fb0 = GetRawFramebulkAt(slot, 0);
630+
631+
if (fb0.tick == 0) {
632+
for (std::string cmd : fb0.commands) {
633+
fb.commands.push_back(cmd);
634+
}
635+
}
636+
}
637+
638+
if (IsUsingTools()) {
639+
ApplyTools(fb, pInfo, PRE_PROCESSING);
640+
641+
if (sar_tas_debug.GetInt() > 0) {
642+
console->Print("(TAS: pretick) %s\n", fb.ToString().c_str());
643+
}
644+
}
645+
646+
return fb;
647+
}
648+
594649
// special tools have to be parsed in input processing part.
595650
// because of alternateticks, a pair of inputs are created and then executed at the same time,
596651
// meaning that second tick in pair reads outdated info.
@@ -603,18 +658,7 @@ void TasPlayer::PostProcess(int slot, void *player, CUserCmd *cmd) {
603658
// every other way of getting time is incorrect due to alternateticks
604659
int tasTick = FetchCurrentPlayerTickBase(player) - startTick;
605660

606-
TasFramebulk fb = GetRawFramebulkAt(slot, tasTick, currentToolsFramebulkIndex[slot]);
607-
608-
// update all tools that needs to be updated
609-
auto fbTick = fb.tick;
610-
fb.tick = tasTick;
611-
if (fbTick == tasTick) {
612-
for (TasToolCommand cmd : fb.toolCmds) {
613-
auto tool = TasTool::GetInstanceByName(slot, cmd.tool->GetName());
614-
if (tool == nullptr) continue;
615-
tool->SetParams(cmd.params);
616-
}
617-
}
661+
TasFramebulk& fb = RequestProcessedFramebulkAt(slot, tasTick);
618662

619663
auto playerInfo = GetPlayerInfo(slot, player, cmd);
620664

@@ -634,21 +678,7 @@ void TasPlayer::PostProcess(int slot, void *player, CUserCmd *cmd) {
634678
return;
635679
}
636680

637-
// applying tools
638-
if (playbackInfo.slots[slot].header.version >= 3) {
639-
// use priority list for newer versions. technically all tools should be in the list
640-
for (std::string toolName : TasTool::priorityList) {
641-
auto tool = TasTool::GetInstanceByName(slot, toolName);
642-
if (tool == nullptr) continue;
643-
tool->Apply(fb, playerInfo);
644-
}
645-
} else {
646-
// use old "earliest first" ordering system (partially also present in TasTool::SetParams)
647-
for (TasTool *tool : TasTool::GetList(slot)) {
648-
tool->Apply(fb, playerInfo);
649-
}
650-
}
651-
681+
ApplyTools(fb, playerInfo, POST_PROCESSING);
652682

653683
// make sure none of the framebulk is NaN
654684
if (std::isnan(fb.moveAnalog.x)) fb.moveAnalog.x = 0;
@@ -717,13 +747,9 @@ void TasPlayer::PostProcess(int slot, void *player, CUserCmd *cmd) {
717747
SE(player)->fieldOff<CUserCmd>("m_hViewModel", 8) /* m_LastCmd */ = *cmd;
718748
}
719749

720-
// put processed framebulk in the list
721-
if (fbTick != tasTick) {
722-
std::vector<std::string> empty;
723-
fb.commands = empty;
750+
if (sar_tas_debug.GetInt() > 0) {
751+
console->Print("(TAS: posttick) %s\n", fb.ToString().c_str());
724752
}
725-
playbackInfo.slots[slot].processedFramebulks.push_back(fb);
726-
727753
tasPlayer->DumpUsercmd(slot, cmd, tasTick, "processed");
728754
}
729755

@@ -756,6 +782,46 @@ void TasPlayer::ApplyMoveAnalog(Vector moveAnalog, CUserCmd *cmd) {
756782
}
757783
}
758784

785+
void TasPlayer::ApplyTools(TasFramebulk &fb, const TasPlayerInfo &pInfo, TasToolProcessingType processType) {
786+
int slot = pInfo.slot;
787+
788+
for (TasToolCommand cmd : fb.toolCmds) {
789+
auto tool = TasTool::GetInstanceByName(slot, cmd.tool->GetName());
790+
if (!CanProcessTool(tool, processType)) continue;
791+
tool->SetParams(cmd.params);
792+
}
793+
794+
FOR_TAS_SCRIPT_VERSIONS_UNTIL(2) {
795+
// use old "earliest first" ordering system (partially also present in TasTool::SetParams)
796+
for (TasTool *tool : TasTool::GetList(slot)) {
797+
if (!CanProcessTool(tool, processType)) continue;
798+
tool->Apply(fb, pInfo);
799+
}
800+
return;
801+
}
802+
803+
// use priority list for newer versions. technically all tools should be in the list
804+
for (std::string toolName : TasTool::priorityList) {
805+
auto tool = TasTool::GetInstanceByName(slot, toolName);
806+
if (!CanProcessTool(tool, processType)) continue;
807+
tool->Apply(fb, pInfo);
808+
}
809+
}
810+
811+
bool TasPlayer::CanProcessTool(TasTool *tool, TasToolProcessingType processType) {
812+
if (tool == nullptr) {
813+
return false;
814+
}
815+
816+
int slot = tool->GetSlot();
817+
FOR_TAS_SCRIPT_VERSIONS_UNTIL(8) {
818+
// old scripts process all tools in post processing
819+
return processType == POST_PROCESSING;
820+
}
821+
822+
return tool->CanProcess(processType);
823+
}
824+
759825
void TasPlayer::DumpUsercmd(int slot, const CUserCmd *cmd, int tick, const char *source) {
760826
if (!sar_tas_dump_usercmd.GetBool()) return;
761827
std::string str = Utils::ssprintf("%s,%d,%.6f,%.6f,%08X,%.6f,%.6f,%.6f", source, tick, cmd->forwardmove, cmd->sidemove, cmd->buttons, cmd->viewangles.x, cmd->viewangles.y, cmd->viewangles.z);

src/Features/Tas/TasPlayer.hpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#pragma once
22

3-
#include "TasScript.hpp"
43
#include "Command.hpp"
54
#include "Features/Feature.hpp"
65
#include "Features/Tas/TasController.hpp"
76
#include "Features/Tas/TasTool.hpp"
8-
#include "Utils/SDK.hpp"
9-
#include "Variable.hpp"
10-
#include "Modules/Engine.hpp"
117
#include "Modules/Client.hpp"
8+
#include "Modules/Engine.hpp"
129
#include "Modules/Server.hpp"
10+
#include "TasScript.hpp"
11+
#include "Utils/SDK.hpp"
12+
#include "Variable.hpp"
1313

1414
#define TAS_SCRIPTS_DIR "tas"
1515
#define TAS_SCRIPT_EXT "p2tas"
@@ -38,7 +38,7 @@ struct TasPlaybackInfo {
3838
if (coopControlSlot >= 0 && slots[1 - coopControlSlot].IsActive()) {
3939
return slots[1 - coopControlSlot];
4040
}
41-
return slots[0].IsActive() ? slots[0] : slots[1];
41+
return slots[0].IsActive() ? slots[0] : slots[1];
4242
}
4343
inline TasScriptHeader GetMainHeader() const { return GetMainScript().header; }
4444
};
@@ -72,11 +72,11 @@ class TasPlayer : public Feature {
7272
int currentTick = 0; // tick position of script player, relative to its starting point.
7373
int lastTick = 0; // last tick of script, relative to its starting point
7474

75-
int wasEnginePaused = false; // Used to check if we need to revert incrementing a tick
75+
int wasEnginePaused = false; // Used to check if we need to revert incrementing a tick
7676

7777
// used to cache last used framebulk to quickly access it for playback
78-
unsigned currentInputFramebulkIndex[2];
79-
unsigned currentToolsFramebulkIndex[2];
78+
unsigned currentRequestRawFramebulkIndex[2];
79+
8080
public:
8181
void Update();
8282
void UpdateServer();
@@ -89,9 +89,8 @@ class TasPlayer : public Feature {
8989
inline bool IsReady() const { return ready; };
9090
inline bool IsRunning() const { return active && startTick != -1; }
9191
inline bool IsPaused() const { return paused; }
92-
inline bool IsUsingTools() const {
93-
return (playbackInfo.slots[0].IsActive() && !playbackInfo.slots[0].IsRaw())
94-
|| (playbackInfo.slots[1].IsActive() && !playbackInfo.slots[1].IsRaw());
92+
inline bool IsUsingTools() const {
93+
return (playbackInfo.slots[0].IsActive() && !playbackInfo.slots[0].IsRaw()) || (playbackInfo.slots[1].IsActive() && !playbackInfo.slots[1].IsRaw());
9594
}
9695
inline int GetScriptVersion(int slot) const { return playbackInfo.slots[slot].header.version; }
9796

@@ -102,15 +101,16 @@ class TasPlayer : public Feature {
102101
void Activate(TasPlaybackInfo info);
103102
void Start();
104103
void PostStart();
105-
void Stop(bool interrupted=false);
106-
void Replay(bool automatic=false);
104+
void Stop(bool interrupted = false);
105+
void Replay(bool automatic = false);
107106

108107
void Pause();
109108
void Resume();
110109
void AdvanceFrame();
111110

112111
TasFramebulk GetRawFramebulkAt(int slot, int tick);
113112
TasFramebulk GetRawFramebulkAt(int slot, int tick, unsigned &cachedIndex);
113+
TasFramebulk &RequestProcessedFramebulkAt(int slot, int tick);
114114

115115
TasPlayerInfo GetPlayerInfo(int slot, void *player, CUserCmd *cmd, bool clientside = false);
116116
int FetchCurrentPlayerTickBase(void *player, bool clientside = false);
@@ -119,9 +119,12 @@ class TasPlayer : public Feature {
119119
void SaveUsercmdDebugs(int slot);
120120
void SavePlayerInfoDebugs(int slot);
121121

122-
void FetchInputs(int slot, TasController *controller);
122+
void FetchInputs(int slot, TasController *controller, CUserCmd *cmd);
123+
TasFramebulk SamplePreProcessedFramebulk(int slot, int tasTick, void *player, CUserCmd *cmd);
123124
void PostProcess(int slot, void *player, CUserCmd *cmd);
124125
void ApplyMoveAnalog(Vector moveAnalog, CUserCmd *cmd);
126+
void ApplyTools(TasFramebulk &fb, const TasPlayerInfo &pInfo, TasToolProcessingType processType);
127+
bool CanProcessTool(TasTool *tool, TasToolProcessingType processType);
125128
void DumpUsercmd(int slot, const CUserCmd *cmd, int tick, const char *source);
126129
void DumpPlayerInfo(int slot, int tick, Vector pos, Vector eye_pos, QAngle ang);
127130

0 commit comments

Comments
 (0)