initial commit for new format

This commit is contained in:
Lexi Quinn 2023-04-04 01:57:29 +10:00
parent 577e88551d
commit 65414c2d64
4 changed files with 924 additions and 0 deletions

187
formatspec.md Normal file
View File

@ -0,0 +1,187 @@
Introduction
============
The quest file format is a declarative computer language. It is made up
of statements that when read by the software define how it should behave and
statements that are written by the software to declare what it recorded.
Each statement begins with a directive followed by options or arguments indented
by one tab on the following lines.
The order of the directives is important, they are read by the program
line by line and may inherit data from those placed above.
If a line does not begin with a recognizable directive, it is ignored.
Care should be taken when inserting comments as one program may recognize
directives that another does not, for this reason double backslashes (//) are
reserved for leaving comments
List of directives
==================
The indented section of the directives reference show example values first,
followed by the range of possible values or brief explaination in brackets
Option directives
-----------------
Any client is free to define their own directives to look for in this section.
These directives override a programs defaults, they should be placed at the
top of the file alongside MetaData directives
Foreground-Color
247, 248, 242 (0-255, 0-255, 0-255)
Background-Color
47, 53, 66 (0-255, 0-255, 0-255)
Best-Color
249, 255, 79 (0-255, 0-255, 0-255)
Ahead-Gaining-Color
24, 240, 31 (0-255, 0-255, 0-255)
Ahead-Losing-Color
79, 255, 85 (0-255, 0-255, 0-255)
Behind-Gaining-Color
255, 79, 79 (0-255, 0-255, 0-255)
Behind-Losing-Color
224, 34, 34 (0-255, 0-255, 0-255)
Comparison
Personal-Best (Personal-Best/World-Record/Sum-of-Best)
Show-Delta-Column
True (True/False)
Show-Segment-Column
True (True/False)
Show-Time-Column
True (True/False)
Show-Comparison-Column
True (True/False)
Toggle-Hotkeys
t (See key name section)
Start-Key
r (See key name section)
Stop-Key
f (See key name section)
Pause-Key
y (See key name section)
Split-Key
e (See key name section)
Unsplit-Key
g (See key name section)
Skip-Key
v (See key name section)
MetaData directives
---------------
These directives provide metadata to runs that follow them,
multiple of these can appear in a single file if multiple
games or categories are run after one another
Title
Elden Ring (Title of the game being run)
Category
Any% (Name of the category being run)
Runner
SuperCoolGuy04 (The name you as the runner are known by)
Segment and Route Directives
----------------------------
Runs by themselves are simply a list of events that occured
(such as splitting or pausing) in the quest language, in order to give meaning
to this data, these more complicated directives are used to define segments
that are played between splits and routes made up of these segments.
Define all your possible segments first, followed by all routes.
If no segments are defined, a single unnamed segment is assumed.
If no routes are defined, a single unnamed route that passes through all
segments in the order of their definition is assumed.
Segment
Shortname
Stage One (A name for the segment that should identify it
within the file and refer to the single
objective completed.
This name should not change.)
Longname
Murderize Chad (The display name for the segment if it should
differ from the proper name, this name can be
changed freely and may be used for
inside-jokes without issue.)
Description
Go to the golden palace and kill
the big dude with the ugly sunglasses
(Optional third argument for use as a more
detailed note or reminder of the objective
for the segment that may be displayed
alongside the name)
Route
Name
Magic Swordless (Name for the route, keeping in mind routes
are not categories, you may simply run
multiple different routes because you're unsure
yet which strategies are the fastest)
Segments
Stage One (The ordered list of segments the route
Stage Two consists of, these names should match the
Stage Four segment Shortnames)
Run Directives
--------------
These directives are much more complicated and are not intended to be written
by a human but rather by the timer software, they will make up the majority
of a file as they are the run history which may be quite long.
These data passed by these directives exists agnostic of segments, route, games,
or categories, rather they are either explicitly matched with metadata that is
applicable, or by default is matched with the last set of metadata declared by
the time of the run directive
Run
Route
Magic Swordless
Start
2016-10-23 10:03:12.034Z
(The first event in a run must be Start, and it
must be accompanied by an RFC 3339 timestamp)
Split
120052 (Following events may be accompanied by
RFC 3339 timestamps or simply a millisecond
offset from the previous event)
Skip
323481
Split
3121111
Pause
421397
Unpause
2016-10-23 11:16:04.175Z
Stop (The last event in a run is always a Stop)
123111
The 'Then' Directive
------------------
The default behaviour the timer should exhibit when multiple games, catagories,
or routes exist in the same quest file is to allow the user to select
which one should be run, but the 'Then' directive instead allows for multiple
routes or catagories to be run one straight after the other as a single longer
"route". The timer should insert a dummy "segment" between the two routes for
the user to spend resetting the game and record the resulting run in two places:
A run directive should be recorded with every event including the dummy split
that can be exported and shared as one marathon attempt
AND
Individual run directives for each of the individual routes should be created
without the other included routes or the dummy split that can contribute to the
attempt history and stats of those individual catagories
If multiple routes exist on both or either side of a Then directive, the routes
to be stitched together should be selectable out of the options given, just as
they are normally.

118
src2/client.c Normal file
View File

@ -0,0 +1,118 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char *argv[]) {
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
char commandcode;
if (argc < 2) {
fprintf(stderr,"usage %s command\n", argv[0]);
exit(0);
}
portno = 8101;
/* Create a socket point */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
server = gethostbyname("localhost");
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
/* Now connect to the server */
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR connecting");
exit(1);
}
if (!strcmp(argv[1], "time")) {
commandcode = 1;
} else if (!strcmp(argv[1], "start")) {
commandcode = 2;
} else if (!strcmp(argv[1], "stop")) {
commandcode = 3;
} else if (!strcmp(argv[1], "kill")) {
commandcode = 4;
} else if (!strcmp(argv[1], "split")) {
commandcode = 5;
} else if (!strcmp(argv[1], "skip")) {
commandcode = 6;
} else if (!strcmp(argv[1], "pause")) {
commandcode = 7;
} else if (!strcmp(argv[1], "resume")) {
commandcode = 8;
} else if (!strcmp(argv[1], "undo")) {
commandcode = 9;
} else if (!strcmp(argv[1], "redo")) {
commandcode = 10;
} else if (!strcmp(argv[1], "foreground")) {
commandcode = 11;
} else if (!strcmp(argv[1], "background")) {
commandcode = 12;
} else {
perror("No valid command given");
exit(1);
}
/* Send message to the server */
n = write(sockfd, &commandcode, 1);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
/* Now read server response */
//bzero(buffer,256);
//read an int response
if (commandcode < 11) {
int x = -1;
n = read(sockfd, &x, sizeof(int));
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
if (x != -1)
printf("%d\n",x);
}
//read a string response
else {
bzero(buffer,256);
n = read(sockfd, &buffer, 255);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
if (buffer != NULL)
printf("%s", buffer);
}
return 0;
}

415
src2/server.c Normal file
View File

@ -0,0 +1,415 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#define NS_PER_S 1000000000
struct timespec finish, delta;
int pausedTime = 0;
bool timerActive = false;
bool paused = false;
bool alive = true;
int timerOffset = 0;
enum event_type {
START,
SPLIT,
SKIP,
PAUSE,
RESUME,
STOP
};
struct run_event {
enum event_type type;
struct timespec time;
};
struct run_event *run;
//Enough to hold a sm64 16 star, can realloc later
int runMaxLength = 12;
int runMarker = 0;
int runMarker2 = 0;
//save file stuff
int files = 0;
char **filePaths = NULL;
char **names, **values;
int valuecount;
void sub_timespec(struct timespec t1, struct timespec t2, struct timespec* td)
{
td->tv_nsec = t2.tv_nsec - t1.tv_nsec;
td->tv_sec = t2.tv_sec - t1.tv_sec;
if (td->tv_sec > 0 && td->tv_nsec < 0) {
td->tv_nsec += NS_PER_S;
td->tv_sec--;
} else if (td->tv_sec < 0 && td->tv_nsec > 0) {
td->tv_nsec -= NS_PER_S;
td->tv_sec++;
}
}
void offset_timespec(int milliseconds, struct timespec* t)
{
//this should leave milliseconds with just the remainder
int second_offset = milliseconds / 1000;
milliseconds -= second_offset * 1000;
int nanosecond_offset = milliseconds * 1000000;
t->tv_nsec -= nanosecond_offset;
if (t->tv_nsec < 0) {
second_offset++;
t->tv_nsec += 1000000000;
}
t->tv_sec -= second_offset;
}
int timespecToMS(struct timespec t)
{
return (t.tv_nsec / 1000000) + (t.tv_sec * 1000);
}
void extend_run()
{
runMaxLength *= 2;
run = realloc(run, sizeof(struct run_event) * runMaxLength);
}
void add_event(enum event_type t)
{
if (runMarker == runMaxLength)
extend_run();
run[runMarker].type = t;
clock_gettime(CLOCK_REALTIME, &run[runMarker].time);
if (t == START)
offset_timespec(timerOffset, &run[runMarker].time);
runMarker++;
runMarker2 = runMarker;
}
void start()
{
//TODO: Save the old run to the file before the new one starts,
//the reason to do this here is it gives the runner a chance to undo
//if they accidentally hit the stop button
//TODO: Clear the run data first
timerActive = true;
add_event(START);
}
void stop()
{
timerActive = false;
add_event(STOP);
//this makes sure the time clients recieve from time
//requests match the time on the stop event
finish = run[runMarker - 1].time;
}
void split()
{
add_event(SPLIT);
}
void skip()
{
add_event(SKIP);
}
void addPauseTime()
{
int pauseEvent = 0;
for (int i = runMarker - 2; i >= 1; i--) {
if (run[i].type == PAUSE) {
pauseEvent = i;
break;
}
}
sub_timespec(run[pauseEvent].time, run[runMarker - 1].time, &delta);
pausedTime += timespecToMS(delta);
}
void subtractPauseTime()
{
int pauseEvent = 0;
for (int i = runMarker - 1; i >= i; i--) {
if (run[i].type == PAUSE) {
pauseEvent = i;
break;
}
}
sub_timespec(run[pauseEvent].time, run[runMarker].time, &delta);
pausedTime -= timespecToMS(delta);
}
void undo()
{
if (runMarker > 0) {
runMarker--;
if (run[runMarker].type == STOP)
timerActive = true;
if (run[runMarker].type == START)
timerActive = false;
if (run[runMarker].type == PAUSE)
paused = false;
if (run[runMarker].type == RESUME) {
paused = true;
subtractPauseTime();
}
}
}
void redo()
{
if (runMarker < runMarker2) {
runMarker++;
if (run[runMarker - 1].type == STOP)
timerActive = false;
if (run[runMarker - 1].type == START)
timerActive = true;
if (run[runMarker - 1].type == PAUSE)
paused = true;
if (run[runMarker - 1].type == RESUME) {
paused = false;
addPauseTime();
}
}
}
//this isnt just called pause() because that would overlap with <unistd.h>
void pause_timer()
{
if (!paused) {
add_event(PAUSE);
paused = true;
}
}
void resume()
{
if (paused) {
add_event(RESUME);
paused = false;
addPauseTime();
}
}
void loadFiles()
{
FILE* fp;
//TODO: for now we're just looking for the metadata values
char buff[255];
char buff2[255];
for (int i = 0; i < files; i++) {
fp = fopen(filePaths[i], "r");
while(1) {
char *x = fgets(buff, 255, fp);
if (buff[0] == '/' && buff[1] == '/' || buff[0] == '\n')
continue;
if (!strcmp(buff, "Segment") || !strcmp(buff, "Route") || x == NULL)
break;
fgets(buff2, 255, fp);
if (buff2[0] == '\t') {
valuecount++;
names = realloc(names, sizeof(char*) * valuecount);
names[valuecount - 1] = malloc(strlen(buff) - 1);
strncpy(names[valuecount - 1], buff, strlen(buff) - 1);
names[valuecount - 1][strlen(buff)] = '\0';
values = realloc(values, sizeof(char*) * valuecount);
values[valuecount - 1] = malloc(strlen(buff2) - 2);
strncpy(values[valuecount - 1], buff2 + 1, strlen(buff2) - 1);
values[valuecount - 1][strlen(buff2)] = '\0';
}
}
fclose(fp);
}
//Print metadata arrays
for (int i = 0; i < valuecount; i++) {
printf("%s | %s", names[i], values[i]);
}
}
//TODO: eventually file loading should support loading multiple files
void addFile(char *path)
{
files++;
filePaths = realloc(filePaths, sizeof(char*) * files);
filePaths[files - 1] = path;
loadFiles();
}
void sendTime(int sock)
{
int n, x;
if (timerActive)
clock_gettime(CLOCK_REALTIME, &finish);
if (paused) {
sub_timespec(run[0].time, run[runMarker - 1].time, &delta);
} else {
sub_timespec(run[0].time, finish, &delta);
}
x = timespecToMS(delta) - pausedTime;
n = write(sock, &x, sizeof(int));
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
void sendValue(int sock, char* name)
{
int n, x;
bool namefound = false;
for(int i = 0; i < valuecount; i++) {
if (!strcmp(names[i], name)) {
x = i;
namefound = true;
}
}
if (namefound)
n = write(sock, values[x], strlen(values[x]));
else
n = write(sock, "DATA NOT PRESENT", 17);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
void doprocessing (int sock)
{
int n;
char commandcode;
n = read(sock,&commandcode,1);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
if (commandcode == 1) {
//printf("Recieved time command\n");
sendTime(sock);
} else if (commandcode == 2) {
printf("Recieved start command\n");
start();
} else if (commandcode == 3) {
printf("Recieved stop command\n");
stop();
} else if (commandcode == 4) {
printf("Recieved kill command\n");
alive = false;
} else if (commandcode == 5) {
printf("Recieved split command\n");
split();
} else if (commandcode == 6) {
printf("Recieved skip command\n");
skip();
} else if (commandcode == 7) {
printf("Recieved pause command\n");
pause_timer();
} else if (commandcode == 8) {
printf("Recieved resume command\n");
resume();
} else if (commandcode == 9) {
printf("Recieved undo command\n");
undo();
} else if (commandcode == 10) {
printf("Recieved redo command\n");
redo();
} else if (commandcode == 11) {
printf("Recieved request for foreground color\n");
sendValue(sock, "Foreground-Color");
} else if (commandcode == 12) {
printf("Recieved request for background color\n");
sendValue(sock, "Background-Color");
} else {
printf("Recieved invalid command code, ignoring...\n");
}
}
int main(int argc, char *argv[])
{
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, pid;
run = malloc(sizeof(struct run_event) * runMaxLength);
//TODO: remove this file testing boilerplate
addFile(argv[1]);
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 8101;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here
* process will go in sleep mode and will wait
* for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
while (alive) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
/* Create child process */
//pid = fork();
pid = 1;
if (pid < 0) {
perror("ERROR on fork");
exit(1);
}
if (pid == 0) {
/* This is the child process */
//close(sockfd);
//doprocessing(newsockfd);
//exit(0);
}
else {
doprocessing(newsockfd);
close(newsockfd);
}
} /* end of while */
free(run);
close(sockfd);
}

204
src2/tui.c Normal file
View File

@ -0,0 +1,204 @@
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <signal.h>
//Big numbies
char bignumbers[4][126] = {
//char bignumbers[4][55] = {
"▄▀▀▄ ▄█ ▄▀▀▄ ▄▀▀▄ ▄ █ █▀▀▀ ▄▀▀ ▀▀▀█ ▄▀▀▄ ▄▀▀▄ ",
"█ █ █ ▄▀ ▄▀ █▄▄█ █▄▄ █▄▄ ▐▌ ▀▄▄▀ ▀▄▄█ ▀ ",
"█ █ █ ▄▀ ▄ █ █ █ █ █ █ █ █ █ ▀ ",
" ▀▀ ▀▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀▀ ▀▀ ▀ ▀▀ ▀▀ ▀ "
};
struct termios base;
int fps = 60;
struct color {
int r;
int g;
int b;
};
struct color b = { 47, 53, 66}; //Background color
struct color f = {247, 248, 242}; //Text foreground color
struct color g = {249, 255, 79}; //Best ever segment time color
struct color ag = { 24, 240, 31}; //Ahead, and gaining time segment color
struct color al = { 79, 255, 85}; //Ahead, but losing time segment color
struct color bg = {255, 79, 79}; //Behind, but gaining time segment color
struct color bl = {224, 34, 34}; //Behind, and losing time segment color
int w, h;
int timestringDigits(int ms)
{
int chars = 4;
if (ms >= 10000)
chars += 1;
if (ms >= 60000)
chars += 2;
if (ms >= 600000)
chars += 1;
if (ms >= 3600000)
chars += 2;
if (ms >= 36000000)
chars += 1;
if (ms >= 360000000)
chars += 1;
return chars;
}
//This function needs an x and y coordinate because the resulting string
//is supposed to be printed across 4 rows so it cant really just return the
//resulting string
void printbigtimestring(int ms, int x, int y)
{
//convert the single cell per character string length into the same
//thing for the big time string, it cant just be a simple multiplication
//because the : and . digits arent as wide as the numbers
//Example string, printing a blank timer with all digits at x=65, y=40.
//\033[40;65H▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄ ▄▀▀▄
//\033[41;65H█ █ █ █ █ █ ▀ █ █ █ █ ▀ █ █ █ █ █ █ █ █
//\033[42;65H█ █ █ █ █ █ ▀ █ █ █ █ ▀ █ █ █ █ █ █ █ █
//\033[43;65H ▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀ ▀▀ ▀▀
char buffer[256];
}
void timestring(char *str, int ms)
{
int msdigits = (ms / 10) % 100;
int seconds = (ms / 1000) % 60;
int minutes = ((ms / 1000) / 60) % 60;
int hours = ((ms / 1000) / 60) / 60;
sprintf(str, "%03d:%02d:%02d.%02d", hours, minutes, seconds, msdigits);
}
void resize(int i)
{
struct winsize ws;
ioctl(1, TIOCGWINSZ, &ws);
w = ws.ws_col;
h = ws.ws_row;
}
void initScreen()
{
struct termios t;
tcgetattr(1, &base);
t = base;
t.c_lflag &= (~ECHO & ~ICANON);
tcsetattr(1, TCSANOW, &t);
//TODO:Figure out why i did this
dup(0);
fcntl(0, F_SETFL, O_NONBLOCK);
printf("\033[?1049h\n"); //Switch to TUI mode (alternate buffer)
printf("\033[?25l\n"); //Hide text cursor
printf("\033[2J\n"); //Clear screen
}
void resetScreen()
{
tcsetattr(1, TCSANOW, &base);
printf("\033[2J\n"); //Clear screen
printf("\033[?1049l\n"); //Switch back to regular mode
printf("\033[?25h\n"); //Show cursor
}
void die(int i)
{
exit(1);
}
void processColorString(struct color *c, char* s)
{
int i = 0;
int length = strlen(s);
char comp[4];
int compcount = 0;
int colorcompsdone = 0;
//TODO: if we know the length now that we're not doing fgetc we dont
//need a while loop; convert to for loop
while (1) {
char x = s[i++];
if (x >= 48 && x <= 57) {
comp[compcount] = x;
compcount++;
}
if (x == 44 || i == length) {
comp[compcount] = '\0';
switch(colorcompsdone) {
case 0:
c->r = atoi(comp);
break;
case 1:
c->g = atoi(comp);
break;
case 2:
c->b = atoi(comp);
}
colorcompsdone++;
compcount = 0;
}
if (i == length)
break;
}
}
int main (int argc, char *argv[])
{
initScreen();
atexit(resetScreen);
signal(SIGTERM, die);
signal(SIGINT, die);
signal(SIGWINCH, resize);
resize(0);
FILE *fp;
char path[1000];
char ti[13];
//Request foreground color from config file
fp = popen("./quest-log foreground", "r");
fgets(path, sizeof(path), fp);
if (strcmp(path, "DATA NOT PRESENT"))
processColorString(&f, path);
printf("\033[38;2;%d;%d;%dm", f.r, f.g, f.b);
pclose(fp);
//Request background color from config file
fp = popen("./quest-log background", "r");
fgets(path, sizeof(path), fp);
if (strcmp(path, "DATA NOT PRESENT"))
processColorString(&b, path);
printf("\033[48;2;%d;%d;%dm", b.r, b.g, b.b);
pclose(fp);
//Set fps from command line argument
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "-fps"))
fps = atoi(argv[i + 1]);
}
while (1) {
int time = 0;
fp = popen("./quest-log time", "r");
if (fp == NULL) {
printf("Failed to run command\n");
exit(1);
}
while (fgets(path, sizeof(path), fp) != NULL) {
time = atoi(path);
printf("\033[2J\n");
timestring(&ti, time);
printf("%s\n", ti + (12 - timestringDigits(time)));
}
pclose(fp);
usleep(1000000 / fps);
}
}