package main import ( "encoding/json" "fmt" "math/rand" "os" "path/filepath" "slices" "sort" "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 PreviousRun struct { Duration float64 `json:"duration"` Date float64 `json:"date"` } 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"` PreviousRuns []PreviousRun `json:"previous_runs"` } func check(e error) { if e != nil { panic(e) } } func CreateBaseRunTimeEntry(jump_map_name string) JumpmapRunTimeEntry { base_entry_str := `{ "nontempusrun": true, "manually_created": true, "id": -1, "duration": 0, "rank": -1, "date": 0, "class": "SOLDIER", "name": "jump_basejson" }` var jump_map JumpmapRunTimeEntry if err := json.Unmarshal([]byte(base_entry_str), &jump_map); err != nil { check(err) } jump_map.Name = jump_map_name return jump_map } 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:]...) }*/ func GetCurtime() float64 { currentTime := time.Now() fractional_nanosecond := float64(currentTime.UnixNano()) unix_seconds := fractional_nanosecond / 1e9 return unix_seconds } 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 GetDatabaseFilePath(jump_map_name string) string { return filepath.Join(mapinfo_database_location, jump_map_name+".json") } func GetRecordFilePath(jump_map_type int, jump_map_name string) string { return filepath.Join("personal", "completed."+GetFileNameFromJumpmapRunTypeEnum(jump_map_type), jump_map_name+".json") } func ReadDatabaseFile(jump_map_name string) JumpDatabaseMapEntry { data, error := os.ReadFile(GetDatabaseFilePath(jump_map_name)) check(error) var jump_map JumpDatabaseMapEntry if err := json.Unmarshal(data, &jump_map); err != nil { check(err) } if jump_map.Name != ExtractBareFilenameFromPath(jump_map_name) { panic("Malformed Map Database entry: " + jump_map_name + " Has: " + jump_map.Name) } return jump_map } func ReadRunRecordFile(jump_map_type int, jump_map_name string) JumpmapRunTimeEntry { data, error := os.ReadFile(GetRecordFilePath(jump_map_type, jump_map_name)) check(error) var jump_map JumpmapRunTimeEntry if err := json.Unmarshal(data, &jump_map); err != nil { check(err) } if jump_map.Name != ExtractBareFilenameFromPath(jump_map_name) { panic("Malformed Run Record: " + jump_map_name + " Has: " + jump_map.Name) } return jump_map } func SubmitNewTime(jump_map_type int, jump_map_name string, duration float64) { record_filepath := GetRecordFilePath(jump_map_type, jump_map_name) var new_entry JumpmapRunTimeEntry // Try to read the file firs _, err := os.Stat(record_filepath) run_exists := err == nil if run_exists { // if it exists, read it and verify run is fast new_entry = ReadRunRecordFile(jump_map_type, jump_map_name) if duration >= new_entry.Duration { if new_entry.Duration == duration { fmt.Printf("Time is duplicate or the same as current time: %f - %s\n", new_entry.Duration, jump_map_name+": "+GetPrettyTime(new_entry.Duration)) } else { how_slow := (new_entry.Duration - duration) fmt.Printf("Time is too slow by %f seconds: %s\n", how_slow, jump_map_name) fmt.Printf("Get Faster by: %s\n", GetPrettyTime(how_slow)) } panic("Time too slow!!! Get faster then try again.") } // back it up ConvertRunToPrevious := func(new_entry JumpmapRunTimeEntry) PreviousRun { var prev_run PreviousRun prev_run.Date = new_entry.Date prev_run.Duration = new_entry.Duration return prev_run } new_entry.PreviousRuns = append(new_entry.PreviousRuns, ConvertRunToPrevious(new_entry)) // Create a fresh database entry } else { new_entry = CreateBaseRunTimeEntry(jump_map_name) } // Set the time within the record new_entry.Duration = duration new_entry.Date = GetCurtime() // Assemble and Write it to a file json_file_data, err := json.MarshalIndent(new_entry, "", "\t") check(err) err = os.WriteFile(record_filepath, json_file_data, 0777) check(err) fmt.Println("Submitted NEW time for map: " + jump_map_name) } func PrintRecord(printable_record any) string { data, error := json.MarshalIndent(printable_record, "", "\t") check(error) return string(data) } func DoesFileExist(filepath string) bool { if _, err := os.Stat(filepath); err == nil { return true } return false } func FindMapNameFromPartial(partial_jump_map_name string) string { if !strings.HasPrefix(partial_jump_map_name, "jump_") && !strings.HasPrefix(partial_jump_map_name, "rj_") { partial_jump_map_name = "jump_" + partial_jump_map_name } found_files, err := filepath.Glob(GetDatabaseFilePath(partial_jump_map_name + "*")) check(err) found_file_count := len(found_files) if found_file_count > 1 { found_map := GetDatabaseFilePath(partial_jump_map_name) if DoesFileExist(found_map) { 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) } } if len(found_files) == 0 { fmt.Println("Unable to find mapname: " + partial_jump_map_name) panic("Attempting to find map that doesnt exist: " + partial_jump_map_name) } // should be just one return ExtractBareFilenameFromPath(found_files[0]) } func GetPrettyTime(in_time float64) string { duration := time.Duration(in_time) * 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 GetPrettyMapTime(jump_map_type int, jump_map_name string) string { run_record := ReadRunRecordFile(jump_map_type, jump_map_name) return GetPrettyTime(run_record.Duration) } 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 PrintMapRuntime(jump_map_type int, jump_map_name string) { fmt.Println("Run time for " + jump_map_name + ": " + GetPrettyMapTime(jump_map_type, jump_map_name)) } func PrintUsageInfo() { fmt.Println("usage: " + os.Args[0] + " [operation] ") fmt.Println("try running \"" + os.Args[0] + " help\" for additional info on operations") } func PrintMapUsageInfo() { fmt.Println("usage: " + os.Args[0] + " [operation] ") } func PrintClassUsageInfo() { fmt.Println("usage: " + os.Args[0] + " [operation] ") } func PrintSubmitTimeInfo() { fmt.Println("usage: " + os.Args[0] + " [operation]