gojump/main.go

493 lines
13 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
)
type JumpDatabaseMapEntry struct {
Authors []struct {
Name string `json:"name"`
Player struct {
SteamID string `json:"steamId"`
Country string `json:"country"`
} `json:"player"`
} `json:"authors"`
ID string `json:"id"`
Name string `json:"name"`
Videos struct {
Soldier string `json:"soldier"`
Demoman string `json:"demoman"`
} `json:"videos"`
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
Zones struct {
Bonus []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"bonus"`
BonusEnd []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"bonusEnd"`
Checkpoint []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"checkpoint"`
Course []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName string `json:"customName"`
} `json:"course"`
CourseEnd []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"courseEnd"`
Linear []any `json:"linear"`
Map []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"map"`
MapEnd []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName any `json:"customName"`
} `json:"mapEnd"`
Misc []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName string `json:"customName"`
} `json:"misc"`
Trick []struct {
ID int `json:"id"`
Map struct {
Tiers struct {
Soldier int `json:"soldier"`
Demoman int `json:"demoman"`
} `json:"tiers"`
} `json:"map"`
Type string `json:"type"`
Zoneindex int `json:"zoneindex"`
CustomName string `json:"customName"`
} `json:"trick"`
} `json:"zones"`
}
type JumpmapRunTimeEntry struct {
Nontempusrun bool `json:"nontempusrun"`
ManuallyCreated bool `json:"manually_created"`
ID int `json:"id"`
Duration float64 `json:"duration"`
Rank int `json:"rank"`
Date float64 `json:"date"`
Class string `json:"class"`
Name string `json:"name"`
}
func check(e error) {
if e != nil {
panic(e)
}
}
func ConvertPrettyTimeToSeconds(time string) float64 {
parts := strings.Split(time, ":")
seconds := 0.0
if len(parts) == 1 {
secs, _ := strconv.ParseFloat(parts[0], 64)
seconds += secs
} else if len(parts) == 2 {
minutes, _ := strconv.ParseFloat(parts[0], 64)
secs, _ := strconv.ParseFloat(parts[1], 64)
seconds += minutes*60 + secs
} else if len(parts) == 3 {
hours, _ := strconv.ParseFloat(parts[0], 64)
minutes, _ := strconv.ParseFloat(parts[1], 64)
secs, _ := strconv.ParseFloat(parts[2], 64)
seconds += hours*3600 + minutes*60 + secs
} else if len(parts) == 4 {
days, _ := strconv.ParseFloat(parts[0], 64)
hours, _ := strconv.ParseFloat(parts[1], 64)
minutes, _ := strconv.ParseFloat(parts[2], 64)
secs, _ := strconv.ParseFloat(parts[3], 64)
seconds += days*86400 + hours*3600 + minutes*60 + secs
}
return seconds
}
func ExtractBareFilenameFromPath(in_path string) string {
baseName := filepath.Base(in_path)
lastDotIndex := strings.LastIndex(baseName, ".")
if lastDotIndex != -1 {
fileNameWithoutExt := baseName[:lastDotIndex]
return fileNameWithoutExt
} else {
// If there's no extension, the whole base name is the filename
return baseName
}
}
/*func RemoveElement(slice []any, s int) []any {
return append(slice[:s], slice[s+1:]...)
}*/
const (
JumpmapTypeSoldier = 0
JumpmapTypeDemoman = 1
)
func GetFileNameFromJumpmapRunTypeEnum(run_type int) string {
if run_type == JumpmapTypeSoldier {
return "soldier"
}
if run_type == JumpmapTypeDemoman {
return "demoman"
}
panic("unknown jumpmap run types!")
// return "unknown"
}
func StringToJumpmapRunType(jump_map_wanted_type string) int {
switch jump_map_wanted_type {
case "s":
fallthrough
case "soldier":
return JumpmapTypeSoldier
case "d":
fallthrough
case "demoman":
return JumpmapTypeDemoman
default:
}
panic("Trying to convert to jumpmap run type (soldier/demo) with incorrect string data!")
}
var mapinfo_database_location = filepath.Join("database", "map_info")
func ReadDatabaseFile(jump_map_name string) JumpDatabaseMapEntry {
data, error := os.ReadFile(filepath.Join(mapinfo_database_location, jump_map_name+".json"))
check(error)
var jump_map JumpDatabaseMapEntry
if err := json.Unmarshal(data, &jump_map); err != nil {
check(err)
}
return jump_map
}
func ReadRunRecordFile(jump_map_type int, jump_map_name string) JumpmapRunTimeEntry {
data, error := os.ReadFile(filepath.Join("personal", "completed."+GetFileNameFromJumpmapRunTypeEnum(jump_map_type), jump_map_name+".json"))
check(error)
var jump_map JumpmapRunTimeEntry
if err := json.Unmarshal(data, &jump_map); err != nil {
check(err)
}
return jump_map
}
func PrintRecord(printable_record any) string {
data, error := json.MarshalIndent(printable_record, "", "\t")
check(error)
return string(data)
}
func FindMapNameFromPartial(partial_jump_map_name string) string {
if !strings.HasPrefix(partial_jump_map_name, "jump_") {
partial_jump_map_name = "jump_" + partial_jump_map_name
}
found_files, err := filepath.Glob(filepath.Join(mapinfo_database_location, partial_jump_map_name+"*.json"))
check(err)
found_file_count := len(found_files)
if found_file_count > 1 {
found_map := filepath.Join(filepath.Join(mapinfo_database_location, partial_jump_map_name+".json"))
if _, err := os.Stat(found_map); err == nil {
return ExtractBareFilenameFromPath(found_map)
} else {
fmt.Printf("There are too many maps that match your request: \"%s\"\n", partial_jump_map_name)
if found_file_count < 5 {
fmt.Println("The following maps match your request:")
fmt.Println(strings.Join(found_files, "\n"))
}
os.Exit(2)
}
}
return ExtractBareFilenameFromPath(found_files[0])
}
func GetPrettyMapTime(jump_map_type int, jump_map_name string) string {
run_record := ReadRunRecordFile(jump_map_type, jump_map_name)
duration := time.Duration(run_record.Duration) * time.Second
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
seconds := int(duration.Seconds()) % 60
return fmt.Sprintf("%dh %dm %ds", hours, minutes, seconds)
}
func GetMapList() []string {
found_files, err := filepath.Glob(filepath.Join(mapinfo_database_location, "*.json"))
check(err)
for index, fle := range found_files {
found_files[index] = ExtractBareFilenameFromPath(fle)
}
return found_files
}
func GetCompletedMapList(jump_map_type int) []string {
found_files, err := filepath.Glob(filepath.Join("personal", "completed."+GetFileNameFromJumpmapRunTypeEnum(jump_map_type), "*.json"))
check(err)
for index, fle := range found_files {
found_files[index] = ExtractBareFilenameFromPath(fle)
}
return found_files
}
func GetMapTier(jump_map_type int, jump_map_name string) int {
map_info := ReadDatabaseFile(jump_map_name)
tier := func() int {
switch jump_map_type {
case JumpmapTypeDemoman:
return map_info.Tiers.Demoman
case JumpmapTypeSoldier:
return map_info.Tiers.Soldier
default:
panic("unselectable JumpmapType enum option other than solly or demo")
}
}()
return tier
}
func IsOnMapIgnorelist(jump_map_name string) bool {
if jump_map_name == "base_jump" {
return true
}
return false
}
func PrintMapUsageInfo() {
fmt.Println("usage: " + os.Args[0] + "[operation] <MAP_NAME> <SOLDIER/DEMOMAN(s/d)>")
}
/*func PrintClassUsageInfo() {
fmt.Println("usage: " + os.Args[0] + "[operation] <SOLDIER/DEMOMAN(s/d)>")
}*/
func PrintUsageInfo() {
fmt.Println("usage: " + os.Args[0] + " [operation] <Additional Args>")
fmt.Println("try running \"" + os.Args[0] + " help\" for additional info on operations")
}
func PrintHelpInfo() {
PrintUsageInfo()
fmt.Println("help - Get the help you are currently seeing")
fmt.Println("get-time - Get a personal maps completion time")
fmt.Println("list-tier - Get the difficulty tier of a particular map")
fmt.Println("list-tiers - Get all of the difficulty tiers listed out, useful for combining with grep!")
fmt.Println("list-maps - Get a list of all maps available")
fmt.Println("convert-time - Convert a time from HH:MM:SS.MS to seconds, for use in storing times in the database")
fmt.Println("get-curtime - Get the current unix date, time in seconds")
}
func main() {
argument_length := len(os.Args)
if argument_length < 2 {
PrintUsageInfo()
os.Exit(42)
}
if os.Args[1] == "help" {
PrintHelpInfo()
os.Exit(0)
}
if os.Args[1] == "get-time" {
if argument_length < 3 {
fmt.Println("Error: No map name provided")
PrintMapUsageInfo()
os.Exit(1)
}
wanted_class := JumpmapTypeSoldier
if argument_length < 4 {
fmt.Println("Warning: No classname provided, using soldier as default")
} else {
wanted_class = StringToJumpmapRunType(os.Args[3])
}
// try {
wanted_map := FindMapNameFromPartial(os.Args[2])
//} catch
fmt.Println("Run time for " + wanted_map + ": " + GetPrettyMapTime(wanted_class, wanted_map))
os.Exit(0)
}
if os.Args[1] == "list-tier" {
if argument_length < 3 {
fmt.Println("Error: No map name provided")
PrintMapUsageInfo()
os.Exit(1)
}
wanted_class := JumpmapTypeSoldier
if argument_length < 4 {
fmt.Println("Warning: No classname provided, using soldier as default")
} else {
wanted_class = StringToJumpmapRunType(os.Args[3])
}
wanted_map := FindMapNameFromPartial(os.Args[2])
fmt.Printf("%s: T%d\n", wanted_map, GetMapTier(wanted_class, wanted_map))
os.Exit(0)
}
if os.Args[1] == "list-tiers" {
wanted_class := JumpmapTypeSoldier
if argument_length < 3 {
fmt.Println("Warning: No classname provided, using soldier as default")
} else {
wanted_class = StringToJumpmapRunType(os.Args[3])
}
map_list := GetMapList()
fmt.Println("Found map tiers:")
for _, mapp := range map_list {
fmt.Printf("%s: T%d\n", mapp, GetMapTier(wanted_class, mapp))
}
os.Exit(0)
}
if os.Args[1] == "list-completed-tiers" {
wanted_class := JumpmapTypeSoldier
if argument_length < 3 {
fmt.Println("Warning: No classname provided, using soldier as default")
} else {
wanted_class = StringToJumpmapRunType(os.Args[3])
}
map_list := GetCompletedMapList(wanted_class)
fmt.Println("Found completed map tiers:")
for _, mapp := range map_list {
if !IsOnMapIgnorelist(mapp) {
fmt.Printf("%s: T%d\n", mapp, GetMapTier(wanted_class, mapp))
}
}
os.Exit(0)
}
if os.Args[1] == "list-uncompleted-tiers" {
wanted_class := JumpmapTypeSoldier
if argument_length < 3 {
fmt.Println("Warning: No classname provided, using soldier as default")
} else {
wanted_class = StringToJumpmapRunType(os.Args[3])
}
map_list := GetMapList()
completed_map_list := GetCompletedMapList(wanted_class)
fmt.Println("Found uncompleted map tiers:")
for _, mapp := range map_list {
if !slices.Contains(completed_map_list, mapp) {
fmt.Printf("%s: T%d\n", mapp, GetMapTier(wanted_class, mapp))
}
}
os.Exit(0)
}
if os.Args[1] == "list-maps" {
map_list := GetMapList()
fmt.Println("Found maps:")
for _, mapp := range map_list {
fmt.Println(mapp)
}
os.Exit(0)
}
if os.Args[1] == "convert-time" {
if argument_length < 3 {
fmt.Println("Error: No time to convert into seconds???...???")
os.Exit(1)
}
fmt.Printf("Converted-Time-To-Seconds: %f\n", ConvertPrettyTimeToSeconds(os.Args[2]))
os.Exit(0)
}
if os.Args[1] == "get-curtime" {
currentTime := time.Now()
fractional_nanosecond := float64(currentTime.UnixNano())
unix_seconds := fractional_nanosecond / 1e9
fmt.Printf("Curtime: %f\n", unix_seconds)
os.Exit(0)
}
PrintUsageInfo()
}