关于 ADOFAI 自制关卡的一些事

· · 休闲·娱乐

其实可以不用 angleData 来存储块之间的角度的,也可以用体积更小的 pathData 来存,下方列出了它们间的关系,左侧是 pathData 字符,右侧是 angleData 值:

R = 0
p = 15
J = 30
E = 45
T = 60
o = 75
U = 90
q = 105
G = 120
Q = 135
H = 150
W = 165
L = 180
x = 195
N = 210
Z = 225
F = 240
V = 255
D = 270
Y = 285
B = 300
C = 315
M = 330
A = 345
! = 999

于是写出以下自动播放代码,当然,你需要 Win32api 和 Boost:

#include<cstdlib>
#include<charconv>
#include<chrono>
#include<filesystem>
#include<fstream>
#include<iostream>
#include<string>
#include<vector>
#include<boost/json/src.hpp>
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
namespace fs=std::filesystem;
int map[128];
auto parsefile(fs::path p)
{
    std::ifstream fin(p);
    while(fin.peek()&128)
        fin.get();
    boost::json::parse_options po;
    po.allow_trailing_commas=1;
    boost::json::stream_parser pa({},po);
    while(1)
    {
        std::string s;
        std::getline(fin,s);
        if(!s.size())
            break;
        pa.write(s);
    }
    fin.close();
    return pa.release();
}
int main()
{
    for(int i=0;i<128;i++)
        map[i]=-1;
    for(int i=0;i<24;i++)
        map["RpJEToUqGQHWLxNZFVDYBCMA"[i]]=i*15;
    map['!']=999;
    fs::path datasav(std::getenv("USERPROFILE")+std::string("/AppData/LocalLow/7th Beat Games/A Dance of Fire and Ice/data.sav"));
    if(fs::is_regular_file(datasav))
    {
        auto presets=parsefile(datasav).get_object()["calibrationPresets"].get_array();
        if(presets.size())
        {
            std::cout<<"Recommended delay"<<(presets.size()>1?"s:":":");
            for(auto i:presets)
                std::cout<<' '<<i.get_object()["inputOffset"].get_int64()<<"ms";
            std::cout<<'\n';
        }
    }
    while(1)
    {
        using namespace std::literals;
        std::string _i;
        std::getline(std::cin,_i);
        fs::path p(_i);
        if(!fs::is_regular_file(p))
        {
            std::cout<<"ERROR: File cannot be read.\n";
            continue;
        }
        auto level=parsefile(p).get_object();
        std::vector<int>angleData;
        if(level.contains("pathData"))
        {
            auto pD=level["pathData"].get_string();
            for(const auto&i:pD)
                angleData.push_back(map[i]);
        }
        else if(level.contains("angleData"))
        {
            auto aD=level["angleData"].get_array();
            for(const auto&i:aD)
            {
                const int x=i.get_int64();
                angleData.push_back(x!=999?(x%360+360)%360:x);
            }
        }
        else
        {
            std::cout<<"ERROR: File cannot be read.\n";
            continue;
        }
        if(!angleData.size())
        {
            std::cout<<"ERROR: Invalid file.\n";
            continue;
        }
        auto settings=level["settings"].get_object();
        std::vector<bool>twirl(angleData.size());
        std::vector<double>bpm(angleData.size());
        std::vector<int>hold(angleData.size());
        std::vector<double>pause(angleData.size(),-0.);
        if(settings["bpm"].is_int64())
            bpm[0]=settings["bpm"].get_int64();
        else
            bpm[0]=settings["bpm"].get_double();
        auto actions=level["actions"].get_array();
        for(auto _:actions)
        {
            auto action=_.get_object();
            if(action.contains("active")&&!action["active"].get_bool())
                continue;
            const auto eventType=action["eventType"].get_string();
            const auto floor=action["floor"].get_int64();
            if(eventType=="Twirl")
                twirl[floor]=1;
            else if(eventType=="SetSpeed")
                if(action["speedType"].get_string()=="Bpm")
                    if(action["beatsPerMinute"].is_int64())
                        bpm[floor]=action["beatsPerMinute"].get_int64();
                    else
                        bpm[floor]=action["beatsPerMinute"].get_double();
                else
                    if(action["bpmMultiplier"].is_int64())
                        bpm[floor]=-action["bpmMultiplier"].get_int64();
                    else
                        bpm[floor]=-action["bpmMultiplier"].get_double();
            else if(eventType=="Pause")
                if(action["duration"].is_int64())
                    pause[floor]=action["duration"].get_int64();
                else
                    pause[floor]=action["duration"].get_double();
            else if(eventType=="Hold")
                hold[floor]=action["duration"].get_int64();
        }
        for(auto i=next(twirl.begin());i!=twirl.end();i++)
            if(*prev(i))
                *i=!*i;
        for(auto i=next(bpm.begin());i!=bpm.end();i++)
            if(*i==0.)
                *i=*prev(i);
            else if(*i<0)
                *i=*prev(i)*-*i;
        bool sf=0;
        for(auto i=1u;i!=angleData.size();i++)
            if(angleData[i-1]==999&&angleData[i]==999||angleData[i]==-1)
            {
                sf=1;
                break;
            }
        if(sf||angleData[0]==999||angleData[0]==-1)
        {
            std::cout<<"ERROR: Unsupported file.\n";
            continue;
        }
        auto len=60s/bpm[0];
        for(auto i=1u;i!=angleData.size();i++)
        {
            if(angleData[i]==999)
                continue;
            if(pause[i]==-0.)
            {
                if(angleData[i-1]==999)
                    if(twirl[i])
                        len+=1min/bpm[i]*((angleData[i]-angleData[i-2]-360)%360+360)/180;
                    else
                        len+=1min/bpm[i]*((angleData[i-2]-angleData[i]-360)%360+360)/180;
                else
                    if(twirl[i])
                        len+=1min/bpm[i]*((angleData[i]-angleData[i-1]-540)%360+360)/180;
                    else
                        len+=1min/bpm[i]*((angleData[i-1]-angleData[i]-540)%360+360)/180;
                len+=2min/bpm[i]*hold[i];
            }
            else
                len+=1min/bpm[i]*(pause[i]+1);
        }
        std::cout<<"Read "<<angleData.size()<<" tile"<<(angleData.size()>1?"s":"")<<". Initial BPM: "<<bpm[0]<<". Length: "<<len.count()<<"s."<<std::endl;
        unsigned begin=0;
        std::getline(std::cin,_i),std::from_chars(_i.data(),_i.data()+_i.size(),begin),begin=std::min<unsigned>(begin,angleData.size());
        std::cout<<"Beginning point: "<<begin<<'.'<<std::endl;
        auto offset=(_i.size()<1||_i[0]<'0')&&settings.contains("offset")?1.ms*settings["offset"].get_int64():0.ms;
        unsigned end=angleData.size();
        std::getline(std::cin,_i),std::from_chars(_i.data(),_i.data()+_i.size(),end),end=std::min<unsigned>(end,angleData.size());
        std::cout<<"Ending point: "<<end<<'.'<<std::endl;
        double speed=1;
        std::getline(std::cin,_i),std::from_chars(_i.data(),_i.data()+_i.size(),speed),speed=std::max(speed,.1);
        std::cout<<"Speed set as: "<<speed<<"x."<<std::endl,offset/=speed;
        for(auto&i:bpm)
            i*=speed;
        double delay=0;
        std::getline(std::cin,_i),std::from_chars(_i.data(),_i.data()+_i.size(),delay);
        std::cout<<"Delay set as: "<<delay<<"ms."<<std::endl;
        for(GetAsyncKeyState(' ');!GetAsyncKeyState(' '););
        const int countdownTicks=settings["countdownTicks"].get_int64();
        auto st=std::chrono::steady_clock::now()+1min/bpm[begin]*countdownTicks+delay*1ms+.6s+offset;
        for(auto i=begin;i!=end;i++)
        {
            if(angleData[i]==999)
                continue;
            if(i!=begin)
                if(pause[i]==-0.)
                {
                    if(angleData[i-1]==999)
                        if(twirl[i])
                            st+=1min/bpm[i]*((angleData[i]-angleData[i-2]-360)%360+360)/180;
                        else
                            st+=1min/bpm[i]*((angleData[i-2]-angleData[i]-360)%360+360)/180;
                    else
                        if(twirl[i])
                            st+=1min/bpm[i]*((angleData[i]-angleData[i-1]-540)%360+360)/180;
                        else
                            st+=1min/bpm[i]*((angleData[i-1]-angleData[i]-540)%360+360)/180;
                    st+=2min/bpm[i]*hold[i];
                }
                else
                    st+=1min/bpm[i]*(pause[i]+1);
            while(std::chrono::steady_clock::now()<st);
            keybd_event(' ',0,0,0),keybd_event(' ',0,KEYEVENTF_KEYUP,0);
        }
    }
    return 0;
}

放一些关卡的 pathData 在这里,纯肉眼识别的。注意:为了尽可能少使用 Twirl,这里的某些 pathData 和原关卡是不一样的。以及,因为懒得标注正常的 SetSpeed 倍数(0.25,0.5,2,4),请自行标注。

3-X THE WIND-UP

5-X The Midnight Train

8-X Jungle City

B-X Thanks For Playing My Game

ML-X La nuit de vif

MO-X EMOMOMO

RJ-X Fear Grows

XH-X Final Hope

XI-X It Go

XM-X Miko Skip

XO-X One Forgotten Night

XS-X Party of Spirits

XT-X Options

新宇宙的东西单独放下面,记得把长按判定调成“无需长按”,自由移动期间无敌调成“启用”。你问长按的长度?请自行测试。

In the beginning, there was darkness. Until someone set themselves aflame. Only then did the universe know light.

T1-X NEW LIFE

T1-EX NEW LIFE

T2-X sing sing red indigo

T2-EX sing sing red indigo

T4-X Third Sun

T4-EX Third Sun