Skip to content

Commit 9b03ca3

Browse files
authored
Merge pull request #349 from p2sr/feat/tas-v9
Feat/tas v9
2 parents a9f1cb3 + f14fb47 commit 9b03ca3

40 files changed

+679
-349
lines changed

docs/p2tas.md

Lines changed: 117 additions & 63 deletions
Large diffs are not rendered by default.

src/Cheats.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include "Features/Routing/EntityInspector.hpp"
1313
#include "Features/Speedrun/SpeedrunTimer.hpp"
1414
#include "Features/Tas/TasParser.hpp"
15-
#include "Features/Tas/TasTools/AutoJumpTool.hpp"
15+
#include "Features/Tas/TasTools/JumpTool.hpp"
1616
#include "Features/Tas/TasTools/StrafeTool.hpp"
1717
#include "Features/Timer/Timer.hpp"
1818
#include "Features/WorkshopList.hpp"

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.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,14 @@ float TasParser::toFloat(std::string str) {
776776
return x;
777777
}
778778

779+
bool TasParser::hasSuffix(const std::string &str, const std::string &suffix) {
780+
return str.size() > suffix.length() && str.substr(str.size() - suffix.length()) == suffix;
781+
}
782+
783+
float TasParser::toFloatAssumeSuffix(std::string str, const std::string &suffix) {
784+
return TasParser::toFloat(str.substr(0, str.size() - suffix.size()));
785+
}
786+
779787
void TasParser::SaveRawScriptToFile(TasScript script) {
780788
std::string fixedName = script.path;
781789
size_t lastdot = fixedName.find_last_of(".");

src/Features/Tas/TasParser.hpp

Lines changed: 19 additions & 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>
@@ -18,11 +18,29 @@ struct TasParserException : public std::exception {
1818
const char *what() const throw() { return msg.c_str(); }
1919
};
2020

21+
struct TasParserArgumentCountException : public TasParserException {
22+
TasParserArgumentCountException(TasTool* tool, int count)
23+
: TasParserException(Utils::ssprintf("Wrong argument count for tool %s: %d", tool->GetName(), count)) {
24+
}
25+
};
26+
27+
struct TasParserArgumentException : public TasParserException {
28+
TasParserArgumentException(TasTool* tool, std::string paramName, std::string arg)
29+
: TasParserException(Utils::ssprintf("Wrong %s argument for tool %s: %s", paramName.c_str(), tool->GetName(), arg.c_str())) {
30+
}
31+
32+
TasParserArgumentException(TasTool *tool, std::string arg)
33+
: TasParserException(Utils::ssprintf("Wrong argument for tool %s: %s", tool->GetName(), arg.c_str())) {
34+
}
35+
};
36+
2137
namespace TasParser {
2238
TasScript ParseFile(TasScript &script, std::string filePath);
2339
TasScript ParseScript(TasScript &script, std::string scriptName, std::string scriptString);
2440
void SaveRawScriptToFile(TasScript script);
2541
std::string SaveRawScriptToString(TasScript script);
2642
int toInt(std::string &str);
2743
float toFloat(std::string str);
44+
bool hasSuffix(const std::string &str, const std::string &suffix);
45+
float toFloatAssumeSuffix(std::string str, const std::string &suffix);
2846
};

src/Features/Tas/TasPlayer.cpp

Lines changed: 142 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
#include <climits>
1919
#include <fstream>
20-
#include "TasTools/AutoJumpTool.hpp"
20+
#include "TasTools/JumpTool.hpp"
2121

2222
Variable sar_tas_debug("sar_tas_debug", "0", 0, 2, "Debug TAS information. 0 - none, 1 - basic, 2 - all.\n");
2323
Variable sar_tas_dump_usercmd("sar_tas_dump_usercmd", "0", "Dump TAS-generated usercmds to a file.\n");
@@ -136,6 +136,19 @@ TasPlayer::~TasPlayer() {
136136
//framebulkQueue[1].clear();
137137
}
138138

139+
bool TasPlayer::IsUsingTools() const {
140+
if (!sar_tas_tools_enabled.GetBool()) {
141+
return false;
142+
}
143+
144+
if (sar_tas_tools_force.GetBool()) {
145+
return true;
146+
}
147+
148+
return (playbackInfo.slots[0].IsActive() && !playbackInfo.slots[0].IsRaw())
149+
|| (playbackInfo.slots[1].IsActive() && !playbackInfo.slots[1].IsRaw());
150+
}
151+
139152
void TasPlayer::Activate(TasPlaybackInfo info) {
140153

141154
if (!info.HasActiveSlot()) return;
@@ -154,8 +167,8 @@ void TasPlayer::Activate(TasPlaybackInfo info) {
154167
for (int slot = 0; slot < 2; ++slot) {
155168
playbackInfo.slots[slot].ClearGeneratedContent();
156169

157-
currentInputFramebulkIndex[slot] = 0;
158-
currentToolsFramebulkIndex[slot] = 0;
170+
currentRequestRawFramebulkIndex[slot] = 0;
171+
159172
}
160173

161174
active = true;
@@ -350,6 +363,29 @@ TasFramebulk TasPlayer::GetRawFramebulkAt(int slot, int tick, unsigned& cachedIn
350363
return playbackInfo.slots[slot].framebulks[cachedIndex];
351364
}
352365

366+
TasFramebulk &TasPlayer::RequestProcessedFramebulkAt(int slot, int tick) {
367+
auto &processed = playbackInfo.slots[slot].processedFramebulks;
368+
auto processedCount = processed.size();
369+
370+
if (processedCount == 0 || processed.back().tick < tick) {
371+
TasFramebulk fb = GetRawFramebulkAt(slot, tick, currentRequestRawFramebulkIndex[slot]);
372+
processed.push_back(fb);
373+
return processed.back();
374+
}
375+
376+
// if it already exists, it should be near the end, as we usually request the newest ones
377+
for (int index = processedCount - 1; index >= 0; --index) {
378+
if (processed[index].tick == tick) {
379+
return processed[index];
380+
} else if (processed[index].tick < tick) {
381+
break;
382+
}
383+
}
384+
385+
console->Warning("TAS processed framebulk for tick %d not found! This should not happen!\n", tick);
386+
return processed.back();
387+
}
388+
353389
TasPlayerInfo TasPlayer::GetPlayerInfo(int slot, void *player, CUserCmd *cmd, bool clientside) {
354390
TasPlayerInfo pi;
355391

@@ -427,7 +463,7 @@ TasPlayerInfo TasPlayer::GetPlayerInfo(int slot, void *player, CUserCmd *cmd, bo
427463

428464
// predict the result of autojump tool so other tools can react appropriately.
429465
FOR_TAS_SCRIPT_VERSIONS_SINCE(8) {
430-
if (autoJumpTool[slot].GetCurrentParams().enabled && autoJumpTool[slot].ShouldJump(pi)) {
466+
if (autoJumpTool[slot].WillJump(pi) || jumpTool[slot].WillJump(pi)) {
431467
pi.willBeGrounded = false;
432468
}
433469
}
@@ -540,7 +576,7 @@ void TasPlayer::SaveProcessedFramebulks() {
540576
we assume the response time for our "virtual controller" to be
541577
non-existing and just let it parse inputs corresponding to given tick.
542578
*/
543-
void TasPlayer::FetchInputs(int slot, TasController *controller) {
579+
void TasPlayer::FetchInputs(int slot, TasController *controller, CUserCmd* cmd) {
544580
// Slight hack! Input fetching (including SteamControllerMove) is
545581
// called through _Host_RunFrame_Input, which is called *before*
546582
// GameFrame (that being called via _Host_RunFrame_Server). Therefore,
@@ -549,36 +585,23 @@ void TasPlayer::FetchInputs(int slot, TasController *controller) {
549585
// said than done since the input fetching code is only run when the
550586
// client is connected, so to match the behaviour we'd probably need
551587
// to actually hook at _Host_RunFrame_Input or CL_Move.
552-
int tick = currentTick + 1;
588+
int tasTick = currentTick + 1;
553589

554-
TasFramebulk fb = GetRawFramebulkAt(slot, tick, currentInputFramebulkIndex[slot]);
590+
auto player = server->GetPlayer(slot + 1);
555591

556-
int fbTick = fb.tick;
557-
558-
if (sar_tas_debug.GetInt() > 0 && fbTick == tick) {
559-
console->Print("%s\n", fb.ToString().c_str());
592+
if (tasTick == 1) {
593+
SamplePreProcessedFramebulk(slot, 0, player, cmd);
560594
}
561595

596+
TasFramebulk fb = SamplePreProcessedFramebulk(slot, tasTick, player, cmd);
597+
562598
controller->SetViewAnalog(fb.viewAnalog.x, fb.viewAnalog.y);
563599
controller->SetMoveAnalog(fb.moveAnalog.x, fb.moveAnalog.y);
564600
for (int i = 0; i < TAS_CONTROLLER_INPUT_COUNT; i++) {
565601
controller->SetButtonState((TasControllerInput)i, fb.buttonStates[i]);
566602
}
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-
}
603+
for (std::string cmd : fb.commands) {
604+
controller->AddCommandToQueue(cmd);
582605
}
583606
}
584607

@@ -591,6 +614,52 @@ static bool IsTaunting(ClientEnt *player) {
591614
return false;
592615
}
593616

617+
TasFramebulk TasPlayer::SamplePreProcessedFramebulk(int slot, int tasTick, void *player, CUserCmd *cmd) {
618+
TasFramebulk& fb = RequestProcessedFramebulkAt(slot, tasTick);
619+
620+
auto fbTick = fb.tick;
621+
fb.tick = tasTick;
622+
bool framebulkUpdated = (fbTick == tasTick);
623+
624+
if (sar_tas_debug.GetInt() > 0 && framebulkUpdated) {
625+
console->Print("(TAS: rawtick) %s\n", fb.ToString().c_str());
626+
}
627+
628+
if (tasTick == 0 || !framebulkUpdated) {
629+
std::vector<std::string> emptyCommands;
630+
fb.commands = emptyCommands;
631+
}
632+
633+
if (!framebulkUpdated) {
634+
std::vector<TasToolCommand> emptyToolCmds;
635+
fb.toolCmds = emptyToolCmds;
636+
}
637+
638+
if (tasTick == 1) {
639+
// on tick 1, we'll run the commands from the bulk at tick 0 because
640+
// of the annoying off-by-one thing explained in FetchInputs
641+
TasFramebulk fb0 = GetRawFramebulkAt(slot, 0);
642+
643+
if (fb0.tick == 0) {
644+
for (std::string cmd : fb0.commands) {
645+
fb.commands.push_back(cmd);
646+
}
647+
}
648+
}
649+
650+
if (IsUsingTools()) {
651+
UpdateTools(slot, fb, PRE_PROCESSING);
652+
auto pInfo = GetPlayerInfo(slot, server->GetPlayer(slot + 1), cmd);
653+
ApplyTools(fb, pInfo, PRE_PROCESSING);
654+
655+
if (sar_tas_debug.GetInt() > 0) {
656+
console->Print("(TAS: pretick) %s\n", fb.ToString().c_str());
657+
}
658+
}
659+
660+
return fb;
661+
}
662+
594663
// special tools have to be parsed in input processing part.
595664
// because of alternateticks, a pair of inputs are created and then executed at the same time,
596665
// meaning that second tick in pair reads outdated info.
@@ -603,19 +672,9 @@ void TasPlayer::PostProcess(int slot, void *player, CUserCmd *cmd) {
603672
// every other way of getting time is incorrect due to alternateticks
604673
int tasTick = FetchCurrentPlayerTickBase(player) - startTick;
605674

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-
}
675+
TasFramebulk& fb = RequestProcessedFramebulkAt(slot, tasTick);
618676

677+
UpdateTools(slot, fb, POST_PROCESSING);
619678
auto playerInfo = GetPlayerInfo(slot, player, cmd);
620679

621680
float orig_forward = cmd->forwardmove;
@@ -634,21 +693,7 @@ void TasPlayer::PostProcess(int slot, void *player, CUserCmd *cmd) {
634693
return;
635694
}
636695

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-
696+
ApplyTools(fb, playerInfo, POST_PROCESSING);
652697

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

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

@@ -756,6 +797,48 @@ void TasPlayer::ApplyMoveAnalog(Vector moveAnalog, CUserCmd *cmd) {
756797
}
757798
}
758799

800+
void TasPlayer::UpdateTools(int slot, const TasFramebulk &fb, TasToolProcessingType processType) {
801+
for (TasToolCommand cmd : fb.toolCmds) {
802+
auto tool = TasTool::GetInstanceByName(slot, cmd.tool->GetName());
803+
if (!CanProcessTool(tool, processType)) continue;
804+
tool->SetParams(cmd.params);
805+
}
806+
}
807+
808+
void TasPlayer::ApplyTools(TasFramebulk &fb, const TasPlayerInfo &pInfo, TasToolProcessingType processType) {
809+
int slot = pInfo.slot;
810+
811+
FOR_TAS_SCRIPT_VERSIONS_UNTIL(2) {
812+
// use old "earliest first" ordering system (partially also present in TasTool::SetParams)
813+
for (TasTool *tool : TasTool::GetList(slot)) {
814+
if (!CanProcessTool(tool, processType)) continue;
815+
tool->Apply(fb, pInfo);
816+
}
817+
return;
818+
}
819+
820+
// use priority list for newer versions. technically all tools should be in the list
821+
for (std::string toolName : TasTool::priorityList) {
822+
auto tool = TasTool::GetInstanceByName(slot, toolName);
823+
if (!CanProcessTool(tool, processType)) continue;
824+
tool->Apply(fb, pInfo);
825+
}
826+
}
827+
828+
bool TasPlayer::CanProcessTool(TasTool *tool, TasToolProcessingType processType) {
829+
if (tool == nullptr) {
830+
return false;
831+
}
832+
833+
int slot = tool->GetSlot();
834+
FOR_TAS_SCRIPT_VERSIONS_UNTIL(8) {
835+
// old scripts process all tools in post processing
836+
return processType == POST_PROCESSING;
837+
}
838+
839+
return tool->CanProcess(processType);
840+
}
841+
759842
void TasPlayer::DumpUsercmd(int slot, const CUserCmd *cmd, int tick, const char *source) {
760843
if (!sar_tas_dump_usercmd.GetBool()) return;
761844
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);

0 commit comments

Comments
 (0)