package main import ( "bytes" "encoding/json" "io" "time" "git.sunturtle.xyz/zephyr/horse" ) type SupportCardID int32 // package horse doesn't have this yet type OldWorkSingleIdleMode struct { Chara SingleModeChara `json:"charaInfo"` StartTime int64 `json:"startTime"` EndTime int64 `json:"endTime"` ProgressLog OldIdleSingleModeProgressLog `json:"progressLogInfo"` } type ProgressInfo struct { Chara SingleModeChara `json:"chara_info"` StartTime string `json:"start_time"` EndTime string `json:"end_time"` DressID int32 `json:"dress_id"` } type WorkIdleSingleMode struct { ProgressInfo ProgressInfo `json:"progress_info"` ProgressLog IdleSingleModeProgressLog `json:"progress_log_info"` End End `json:"end_info"` OldChara *SingleModeChara `json:"chara_info"` } type Skill struct { SkillID horse.SkillID `json:"skill_id"` Level int32 `json:"level"` } type SkillTip struct { GroupID horse.SkillGroupID `json:"group_id"` Rarity int32 `json:"rarity"` Level int32 `json:"level"` } type SingleModeSupportCard struct { Position int32 `json:"position"` SupportCardID SupportCardID `json:"support_card_id"` LimitBreakCount int32 `json:"limit_break_count"` Exp int32 `json:"exp"` TrainingPartnerState int32 `json:"training_partner_state"` OwnerViewerID int64 `json:"owner_viewer_id"` RentalType int32 `json:"rental_type"` } type GroupOuting struct { CharaID horse.CharacterID `json:"chara_id"` IsOuting int32 `json:"is_outing"` StoryStep int32 `json:"story_step"` } type Evaluation struct { TargetID int32 `json:"target_id"` TrainingPartner int32 `json:"training_partner_id"` Evaluation int32 `json:"evaluation"` IsOuting int32 `json:"is_outing"` StoryStep int32 `json:"story_step"` IsAppear int32 `json:"is_appear"` GroupOutingInfo []GroupOuting `json:"group_outing_info_array"` } type TrainingLevel struct { CommandID int32 `json:"command_id"` Level int32 `json:"level"` } type GuestOuting struct { SupportCardID SupportCardID `json:"support_card_id"` StoryStep int32 `json:"story_step"` GroupOutingInfo []GroupOuting `json:"group_outing_info_array"` } type SingleModeSkillUpgrade struct { ConditionID int32 `json:"condition_id"` TotalCount int32 `json:"total_count"` CurrentCount int32 `json:"current_count"` } type SingleModeChara struct { SingleModeCharaID int32 `json:"single_mode_chara_id"` CardID horse.UmaID `json:"card_id"` CharaGrade int32 `json:"chara_grade"` Speed int32 `json:"speed"` Stamina int32 `json:"stamina"` Power int32 `json:"power"` Wiz int32 `json:"wiz"` Guts int32 `json:"guts"` Vital int32 `json:"vital"` MaxSpeed int32 `json:"max_speed"` MaxStamina int32 `json:"max_stamina"` MaxPower int32 `json:"max_power"` MaxWiz int32 `json:"max_wiz"` MaxGuts int32 `json:"max_guts"` DefaultMaxSpeed int32 `json:"default_max_speed"` DefaultMaxStamina int32 `json:"default_max_stamina"` DefaultMaxPower int32 `json:"default_max_power"` DefaultMaxWiz int32 `json:"default_max_wiz"` DefaultMaxGuts int32 `json:"default_max_guts"` MaxVital int32 `json:"max_vital"` Motivation int32 `json:"motivation"` Fans int32 `json:"fans"` Rarity int32 `json:"rarity"` RaceProgramID int32 `json:"race_program_id"` ReserveRaceProgramID int32 `json:"reserve_race_program_id"` RaceRunningStyle int32 `json:"race_running_style"` IsShortRace int32 `json:"is_short_race"` TalentLevel int32 `json:"talent_level"` Skills []Skill `json:"skill_array"` DisableSkillIDs []horse.SkillID `json:"disable_skill_id_array"` SkillTips []SkillTip `json:"skill_tips_array"` SupportCards []SingleModeSupportCard `json:"support_card_array"` SuccessionTrainedCharaID1 int32 `json:"succession_trained_chara_id_1"` SuccessionTrainedCharaID2 int32 `json:"succession_trained_chara_id_2"` ProperDistanceShort horse.AptitudeLevel `json:"proper_distance_short"` ProperDistanceMile horse.AptitudeLevel `json:"proper_distance_mile"` ProperDistanceMiddle horse.AptitudeLevel `json:"proper_distance_middle"` ProperDistanceLong horse.AptitudeLevel `json:"proper_distance_long"` ProperRunningStyleNige horse.AptitudeLevel `json:"proper_running_style_nige"` ProperRunningStyleSenko horse.AptitudeLevel `json:"proper_running_style_senko"` ProperRunningStyleSashi horse.AptitudeLevel `json:"proper_running_style_sashi"` ProperRunningStyleOikomi horse.AptitudeLevel `json:"proper_running_style_oikomi"` ProperGroundTurf horse.AptitudeLevel `json:"proper_ground_turf"` ProperGroundDirt horse.AptitudeLevel `json:"proper_ground_dirt"` Turn int32 `json:"turn"` SkillPoint int32 `json:"skill_point"` ShortCutState int32 `json:"short_cut_state"` State int32 `json:"state"` PlayingState int32 `json:"playing_state"` ScenarioID horse.ScenarioID `json:"scenario_id"` RouteID int32 `json:"route_id"` StartTime string `json:"start_time"` Evaluations []Evaluation `json:"evaluation_info_array"` TrainingLevels []TrainingLevel `json:"training_level_info_array"` NicknameIDs []int32 `json:"nickname_id_array"` CharaEffectIDs []int32 `json:"chara_effect_id_array"` RouteRaceIDs []int32 `json:"route_race_id_array"` GuestOutings []GuestOuting `json:"guest_outing_info_array"` SkillUpgrades []SingleModeSkillUpgrade `json:"skill_upgrade_info_array"` } type OldCharaEffectLog struct { CharaEffectID int32 `json:"charaEffectId"` IsActive bool `json:"isActive"` } type CharaEffectLog struct { CharaEffectID int32 `json:"chara_effect_id"` IsActive bool `json:"is_active"` } type IdleSingleModeSignedInt struct { Sign int32 `json:"sign"` Value int32 `json:"value"` } type OldIdleSingleModeGainInfo struct { Speed IdleSingleModeSignedInt `json:"speed"` Stamina IdleSingleModeSignedInt `json:"stamina"` Power IdleSingleModeSignedInt `json:"power"` Wiz IdleSingleModeSignedInt `json:"wiz"` Guts IdleSingleModeSignedInt `json:"guts"` MaxSpeed int32 `json:"maxSpeed"` MaxStamina int32 `json:"maxStamina"` MaxPower int32 `json:"maxPower"` MaxWiz int32 `json:"maxWiz"` MaxGuts int32 `json:"maxGuts"` ProperDistanceShort horse.AptitudeLevel `json:"properDistanceShort"` ProperDistanceMile horse.AptitudeLevel `json:"properDistanceMile"` ProperDistanceMiddle horse.AptitudeLevel `json:"properDistanceMiddle"` ProperDistanceLong horse.AptitudeLevel `json:"properDistanceLong"` ProperRunningStyleNige horse.AptitudeLevel `json:"properRunningStyleNige"` ProperRunningStyleSenko horse.AptitudeLevel `json:"properRunningStyleSenko"` ProperRunningStyleSashi horse.AptitudeLevel `json:"properRunningStyleSashi"` ProperRunningStyleOikomi horse.AptitudeLevel `json:"properRunningStyleOikomi"` ProperGroundTurf horse.AptitudeLevel `json:"properGroundTurf"` ProperGroundDirt horse.AptitudeLevel `json:"properGroundDirt"` SkillPoint int32 `json:"skillPoint"` SkillTips []SkillTip `json:"skillTips"` } type IdleSingleModeGainInfo struct { Speed IdleSingleModeSignedInt `json:"speed"` Stamina IdleSingleModeSignedInt `json:"stamina"` Power IdleSingleModeSignedInt `json:"power"` Wiz IdleSingleModeSignedInt `json:"wiz"` Guts IdleSingleModeSignedInt `json:"guts"` MaxSpeed int32 `json:"max_speed"` MaxStamina int32 `json:"max_stamina"` MaxPower int32 `json:"max_power"` MaxWiz int32 `json:"max_wiz"` MaxGuts int32 `json:"max_guts"` ProperDistanceShort horse.AptitudeLevel `json:"proper_distance_short"` ProperDistanceMile horse.AptitudeLevel `json:"proper_distance_mile"` ProperDistanceMiddle horse.AptitudeLevel `json:"proper_distance_middle"` ProperDistanceLong horse.AptitudeLevel `json:"proper_distance_long"` ProperRunningStyleNige horse.AptitudeLevel `json:"proper_running_style_nige"` ProperRunningStyleSenko horse.AptitudeLevel `json:"proper_running_style_senko"` ProperRunningStyleSashi horse.AptitudeLevel `json:"proper_running_style_sashi"` ProperRunningStyleOikomi horse.AptitudeLevel `json:"proper_running_style_oikomi"` ProperGroundTurf horse.AptitudeLevel `json:"proper_ground_turf"` ProperGroundDirt horse.AptitudeLevel `json:"proper_ground_dirt"` SkillPoint int32 `json:"skill_point"` SkillTips []SkillTip `json:"skill_tips_array"` } type OldIdleSingleModeSupportCardGain struct { SupportCardID SupportCardID `json:"supportCardId"` GainInfo OldIdleSingleModeGainInfo `json:"gainInfo"` } type IdleSingleModeSupportCardGain struct { SupportCardID SupportCardID `json:"support_card_id"` GainInfo IdleSingleModeGainInfo `json:"gain_info"` } type OldFactor struct { FactorID horse.SparkID `json:"factorId"` Level int32 `json:"level"` } type Factor struct { FactorID horse.SparkID `json:"factor_id"` Level int32 `json:"level"` } type OldIdleSingleModeSuccessionFactorGain struct { Year int32 `json:"year"` GainFactorInfo []OldFactor `json:"gainFactorInfoArray"` } type IdleSingleModeSuccessionFactorGain struct { Year int32 `json:"year"` GainFactorInfo []Factor `json:"gain_factor_info_array"` } type SingleRaceHistory struct { Turn int32 `json:"turn"` ProgramID int32 `json:"program_id"` Weather int32 `json:"weather"` GroundCondition int32 `json:"ground_condition"` RunningStyle int32 `json:"running_style"` ResultRank int32 `json:"result_rank"` FrameOrder int32 `json:"frame_order"` NPCCount int32 `json:"npc_count"` } type RaceReward struct { ItemType int32 `json:"item_type"` ItemID int32 `json:"item_id"` ItemNum int32 `json:"item_num"` } type RaceRewardLimit struct { RewardID int32 `json:"reward_id"` ItemType int32 `json:"item_type"` ItemID int32 `json:"item_id"` ItemNum int32 `json:"item_num"` RestCount int32 `json:"rest_count"` } type CharaRaceReward struct { ResultRank int32 `json:"result_rank"` ResultTime int32 `json:"result_time"` RaceReward []RaceReward `json:"race_reward"` RaceRewardBonus []RaceReward `json:"race_reward_bonus"` RaceRewardPlusBonus []RaceReward `json:"race_reward_plus_bonus"` RaceRewardBonusWin []RaceReward `json:"race_reward_bonus_win"` RaceRewardLimit []RaceRewardLimit `json:"race_reward_limit"` GainedFans int32 `json:"gained_fans"` CampaignIDs []int32 `json:"campaign_id_array"` } type IdleSingleModeRaceHistory struct { RaceHistory SingleRaceHistory `json:"race_history"` RaceRewardInfo CharaRaceReward `json:"race_reward_info"` LoseTipsID int32 `json:"lose_tips_id"` } type OldIdleSingleModeProgressLog struct { CharaEffectLog []OldCharaEffectLog `json:"charaEffectLog"` SupportCardGains []OldIdleSingleModeSupportCardGain `json:"supportCardGainInfo"` EventGain OldIdleSingleModeGainInfo `json:"eventGainInfo"` SuccessionGain OldIdleSingleModeGainInfo `json:"successionGainInfo"` SuccessionFactorGains []OldIdleSingleModeSuccessionFactorGain `json:"successionFactorGainArray"` RaceHistory []IdleSingleModeRaceHistory `json:"raceHistoryArray"` GainSkillIDs []horse.SkillID `json:"gainSkillIdArray"` TotalSkillPoint int32 `json:"totalSkillPoint"` } type IdleSingleModeProgressLog struct { CharaEffectLog []CharaEffectLog `json:"chara_effect_log_array"` SupportCardGains []IdleSingleModeSupportCardGain `json:"support_card_gain_info_array"` EventGain IdleSingleModeGainInfo `json:"event_gain_info"` SuccessionGain IdleSingleModeGainInfo `json:"succession_gain_info"` SuccessionFactorGains []IdleSingleModeSuccessionFactorGain `json:"succession_factor_gain_array"` RaceHistory []IdleSingleModeRaceHistory `json:"race_history_array"` GainSkillIDs []horse.SkillID `json:"gain_skill_id_array"` TotalSkillPoint int32 `json:"total_skill_point"` } type AddItem struct { ItemID int32 `json:"item_id"` Number int32 `json:"number"` } type RewardSummary struct { AddItem []AddItem `json:"add_item_list"` } type End struct { Chara SingleModeChara `json:"chara_info"` CharaButWerseterIsOverworked *SingleModeChara `json:"chara_starting_info"` RewardSummary RewardSummary `json:"reward_summary_info"` } func loadISM(r io.Reader) (WorkIdleSingleMode, error) { // Since we have two potential types to read at the top level, // decode one message and try it both ways. var b json.RawMessage var err error if err = json.NewDecoder(r).Decode(&b); err != nil { return WorkIdleSingleMode{}, err } { var ism WorkIdleSingleMode d := json.NewDecoder(bytes.NewReader(b)) d.DisallowUnknownFields() err = d.Decode(&ism) if err == nil { // NOTE(zeph): this is for no error // If old fields are populated, move them to the correct ones. if ism.End.CharaButWerseterIsOverworked != nil { ism.End.Chara = *ism.End.CharaButWerseterIsOverworked ism.End.CharaButWerseterIsOverworked = nil } if ism.OldChara != nil { ism.ProgressInfo.Chara = *ism.OldChara ism.OldChara = nil } return ism, nil } } { var ism OldWorkSingleIdleMode d := json.NewDecoder(bytes.NewReader(b)) d.DisallowUnknownFields() // Use a new error variable here. // If this fails, we want to return the reason we didn't use the API type instead. if err := d.Decode(&ism); err == nil { // NOTE(zeph): this is for no error return unold(ism), nil } } // NOTE(zeph): this is the error from decoding the API type, not the validation type. return WorkIdleSingleMode{}, err } func unold(old OldWorkSingleIdleMode) WorkIdleSingleMode { return WorkIdleSingleMode{ ProgressInfo: ProgressInfo{ Chara: old.Chara, StartTime: time.Unix(old.StartTime, 0).Format(time.DateTime), EndTime: time.Unix(old.EndTime, 0).Format(time.DateTime), }, ProgressLog: IdleSingleModeProgressLog{ CharaEffectLog: unoldEffects(old.ProgressLog.CharaEffectLog), SupportCardGains: unoldCardGains(old.ProgressLog.SupportCardGains), EventGain: IdleSingleModeGainInfo(old.ProgressLog.EventGain), SuccessionGain: IdleSingleModeGainInfo(old.ProgressLog.SuccessionGain), SuccessionFactorGains: unoldFactorGains(old.ProgressLog.SuccessionFactorGains), RaceHistory: old.ProgressLog.RaceHistory, GainSkillIDs: old.ProgressLog.GainSkillIDs, TotalSkillPoint: old.ProgressLog.TotalSkillPoint, }, // We could try to roll up starting numbers plus gains, // but only a few of these files exist anyway. // Not worth the effort. End: End{}, } } func unoldEffects(olds []OldCharaEffectLog) []CharaEffectLog { new := make([]CharaEffectLog, len(olds)) for i, old := range olds { new[i] = CharaEffectLog(old) } return new } func unoldCardGains(olds []OldIdleSingleModeSupportCardGain) []IdleSingleModeSupportCardGain { new := make([]IdleSingleModeSupportCardGain, len(olds)) for i, old := range olds { new[i] = IdleSingleModeSupportCardGain{ SupportCardID: old.SupportCardID, GainInfo: IdleSingleModeGainInfo(old.GainInfo), } } return new } func unoldFactorGains(olds []OldIdleSingleModeSuccessionFactorGain) []IdleSingleModeSuccessionFactorGain { new := make([]IdleSingleModeSuccessionFactorGain, len(olds)) for i, old := range olds { new[i] = IdleSingleModeSuccessionFactorGain{ Year: old.Year, GainFactorInfo: unoldFactors(old.GainFactorInfo), } } return new } func unoldFactors(olds []OldFactor) []Factor { new := make([]Factor, len(olds)) for i, old := range olds { new[i] = Factor(old) } return new }