1use std::collections::HashMap;
2
3use chrono::{DateTime, Local};
4use enum_map::EnumMap;
5use log::warn;
6use num_derive::FromPrimitive;
7use num_traits::FromPrimitive;
8
9use super::{
10 AttributeType, Class, Emblem, Flag, Item, Potion, Race, Reward, SFError,
11 ServerTime,
12 character::{Mount, Portrait},
13 guild::GuildRank,
14 items::Equipment,
15};
16use crate::{PlayerId, misc::*};
17
18#[derive(Debug, Clone, Default)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Mail {
21 pub combat_log: Vec<CombatLogEntry>,
23 pub inbox_capacity: u16,
25 pub inbox: Vec<InboxEntry>,
27 pub news_inbox: Vec<NewsEntry>,
29 pub claimables: Vec<ClaimableMail>,
31 pub open_msg: Option<String>,
34 pub open_claimable: Option<ClaimablePreview>,
37}
38
39#[derive(Debug, Clone, Default)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct HallOfFames {
44 pub players_total: u32,
46 pub players: Vec<HallOfFamePlayer>,
48
49 pub guilds_total: Option<u32>,
52 pub guilds: Vec<HallOfFameGuild>,
54
55 pub fortresses_total: Option<u32>,
58 pub fortresses: Vec<HallOfFameFortress>,
60
61 pub pets_total: Option<u32>,
64 pub pets: Vec<HallOfFamePets>,
66
67 pub hellevator_total: Option<u32>,
68 pub hellevator: Vec<HallOfFameHellevator>,
69
70 pub underworlds_total: Option<u32>,
73 pub underworlds: Vec<HallOfFameUnderworld>,
75}
76
77#[derive(Debug, Clone, Default)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct HallOfFameHellevator {
80 pub rank: usize,
81 pub name: String,
82 pub tokens: u64,
83}
84
85#[derive(Debug, Clone, Default)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct Lookup {
90 players: HashMap<PlayerId, OtherPlayer>,
93 name_to_id: HashMap<String, PlayerId>,
94
95 pub guilds: HashMap<String, OtherGuild>,
97}
98
99impl Lookup {
100 pub(crate) fn insert_lookup(&mut self, other: OtherPlayer) {
101 if other.name.is_empty() || other.player_id == 0 {
102 warn!("Skipping invalid player insert");
103 return;
104 }
105 self.name_to_id.insert(other.name.clone(), other.player_id);
106 self.players.insert(other.player_id, other);
107 }
108
109 #[must_use]
111 pub fn lookup_pid(&self, pid: PlayerId) -> Option<&OtherPlayer> {
112 self.players.get(&pid)
113 }
114
115 #[must_use]
117 pub fn lookup_name(&self, name: &str) -> Option<&OtherPlayer> {
118 let other_pos = self.name_to_id.get(name)?;
119 self.players.get(other_pos)
120 }
121
122 #[allow(clippy::must_use_unit)]
124 pub fn remove_pid(&mut self, pid: PlayerId) -> Option<OtherPlayer> {
125 self.players.remove(&pid)
126 }
127
128 #[allow(clippy::must_use_unit)]
130 pub fn remove_name(&mut self, name: &str) -> Option<OtherPlayer> {
131 let other_pos = self.name_to_id.remove(name)?;
132 self.players.remove(&other_pos)
133 }
134
135 pub fn reset_lookups(&mut self) {
137 self.players = HashMap::default();
138 self.name_to_id = HashMap::default();
139 }
140}
141
142#[derive(Debug, Default, Clone)]
145#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
146pub struct HallOfFamePlayer {
147 pub rank: u32,
149 pub name: String,
151 pub guild: Option<String>,
154 pub level: u32,
156 pub honor: u32,
158 pub class: Class,
160 pub flag: Option<Flag>,
162}
163
164impl HallOfFamePlayer {
165 pub(crate) fn parse(val: &str) -> Result<Self, SFError> {
166 let data: Vec<_> = val.split(',').collect();
167 let rank = data.cfsuget(0, "hof player rank")?;
168 let name = data.cget(1, "hof player name")?.to_string();
169 let guild = Some(data.cget(2, "hof player guild")?.to_string())
170 .filter(|a| !a.is_empty());
171 let level = data.cfsuget(3, "hof player level")?;
172 let honor = data.cfsuget(4, "hof player fame")?;
173 let class: i64 = data.cfsuget(5, "hof player class")?;
174 let Some(class) = FromPrimitive::from_i64(class - 1) else {
175 warn!("Invalid hof class: {class} - {data:?}");
176 return Err(SFError::ParsingError(
177 "hof player class",
178 class.to_string(),
179 ));
180 };
181
182 let raw_flag = data.get(6).copied().unwrap_or_default();
183 let flag = Flag::parse(raw_flag);
184
185 Ok(HallOfFamePlayer {
186 rank,
187 name,
188 guild,
189 level,
190 honor,
191 class,
192 flag,
193 })
194 }
195}
196
197#[derive(Debug, Default, Clone)]
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201pub struct HallOfFameGuild {
202 pub name: String,
204 pub rank: u32,
206 pub leader: String,
208 pub member_count: u32,
210 pub honor: u32,
212 pub is_attacked: bool,
214}
215
216impl HallOfFameGuild {
217 pub(crate) fn parse(val: &str) -> Result<Self, SFError> {
218 let data: Vec<_> = val.split(',').collect();
219 let rank = data.cfsuget(0, "hof guild rank")?;
220 let name = data.cget(1, "hof guild name")?.to_string();
221 let leader = data.cget(2, "hof guild leader")?.to_string();
222 let member = data.cfsuget(3, "hof guild member")?;
223 let honor = data.cfsuget(4, "hof guild fame")?;
224 let attack_status: u8 = data.cfsuget(5, "hof guild atk")?;
225
226 Ok(HallOfFameGuild {
227 rank,
228 name,
229 leader,
230 member_count: member,
231 honor,
232 is_attacked: attack_status == 1u8,
233 })
234 }
235}
236
237impl HallOfFamePets {
238 pub(crate) fn parse(val: &str) -> Result<Self, SFError> {
239 let data: Vec<_> = val.split(',').collect();
240 let rank = data.cfsuget(0, "hof pet rank")?;
241 let name = data.cget(1, "hof pet player")?.to_string();
242 let guild = Some(data.cget(2, "hof pet guild")?.to_string())
243 .filter(|a| !a.is_empty());
244 let collected = data.cfsuget(3, "hof pets collected")?;
245 let honor = data.cfsuget(4, "hof pets fame")?;
246 let unknown = data.cfsuget(5, "hof pets uk")?;
247
248 Ok(HallOfFamePets {
249 name,
250 rank,
251 guild,
252 collected,
253 honor,
254 unknown,
255 })
256 }
257}
258
259impl HallOfFameFortress {
260 pub(crate) fn parse(val: &str) -> Result<Self, SFError> {
261 let data: Vec<_> = val.split(',').collect();
262 let rank = data.cfsuget(0, "hof ft rank")?;
263 let name = data.cget(1, "hof ft player")?.to_string();
264 let guild = Some(data.cget(2, "hof ft guild")?.to_string())
265 .filter(|a| !a.is_empty());
266 let upgrade = data.cfsuget(3, "hof ft collected")?;
267 let honor = data.cfsuget(4, "hof ft fame")?;
268
269 Ok(HallOfFameFortress {
270 name,
271 rank,
272 guild,
273 upgrade,
274 honor,
275 })
276 }
277}
278
279impl HallOfFameUnderworld {
280 pub(crate) fn parse(val: &str) -> Result<Self, SFError> {
281 let data: Vec<_> = val.split(',').collect();
282 let rank = data.cfsuget(0, "hof ft rank")?;
283 let name = data.cget(1, "hof ft player")?.to_string();
284 let guild = Some(data.cget(2, "hof ft guild")?.to_string())
285 .filter(|a| !a.is_empty());
286 let upgrade = data.cfsuget(3, "hof ft collected")?;
287 let honor = data.cfsuget(4, "hof ft fame")?;
288 let unknown = data.cfsuget(5, "hof pets uk")?;
289
290 Ok(HallOfFameUnderworld {
291 rank,
292 name,
293 guild,
294 upgrade,
295 honor,
296 unknown,
297 })
298 }
299}
300
301#[derive(Debug, Default, Clone)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
304pub struct HallOfFameFortress {
305 pub name: String,
307 pub rank: u32,
309 pub guild: Option<String>,
312 pub upgrade: u32,
314 pub honor: u32,
316}
317
318#[derive(Debug, Default, Clone)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub struct HallOfFamePets {
322 pub name: String,
324 pub rank: u32,
326 pub guild: Option<String>,
329 pub collected: u32,
331 pub honor: u32,
333 pub unknown: i64,
336}
337
338#[derive(Debug, Default, Clone)]
340#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
341pub struct HallOfFameUnderworld {
342 pub rank: u32,
344 pub name: String,
346 pub guild: Option<String>,
349 pub upgrade: u32,
351 pub honor: u32,
353 pub unknown: i64,
356}
357
358#[derive(Debug, Default, Clone)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362pub struct OtherPlayer {
363 pub player_id: PlayerId,
366 pub name: String,
368 pub level: u16,
370 pub description: String,
372 pub guild: Option<String>,
374 #[deprecated = "Since server update v30.500, this field is no longer \
376 available and will be removed from the API in the future"]
377 pub guild_joined: Option<DateTime<Local>>,
378 pub mount: Option<Mount>,
380 pub mount_end: Option<DateTime<Local>>,
382 pub portrait: Portrait,
384 pub relationship: Relationship,
386 pub wall_combat_lvl: u16,
388 pub equipment: Equipment,
390
391 pub experience: u64,
392 pub next_level_xp: u64,
393
394 pub honor: u32,
395 pub rank: u32,
396 pub portal_hp_bonus: u32,
398 pub portal_dmg_bonus: u32,
400 pub attribute_basis: EnumMap<AttributeType, u32>,
403 pub attribute_additions: EnumMap<AttributeType, u32>,
405 pub attribute_times_bought: EnumMap<AttributeType, u32>,
407 pub attribute_pet_bonus: EnumMap<AttributeType, u32>,
409 pub class: Class,
411 pub race: Race,
413 pub scrapbook_count: Option<u32>,
415 pub active_potions: [Option<Potion>; 3],
417 pub armor: u64,
419 pub min_damage: u32,
421 pub max_damage: u32,
423 pub fortress: Option<OtherFortress>,
425 pub gladiator_lvl: u32,
427 pub is_vip: bool,
429}
430
431#[derive(Debug, Default, Clone)]
432#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
433pub struct OtherFortress {
434 pub upgrade_count: u32,
436 pub soldier_advice: u16,
439 pub fortifications_level: u16,
441 pub lootable_wood: u64,
444 pub lootable_stone: u64,
447 pub archer_count: u16,
449 pub mage_count: u16,
451 pub rank: u32,
453}
454
455#[derive(Debug, Default, Clone, FromPrimitive, Copy, PartialEq)]
456#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
457pub enum Relationship {
458 #[default]
459 Ignored = -1,
460 Normal = 0,
461 Friend = 1,
462}
463
464impl OtherPlayer {
465 pub(crate) fn update_pet_bonus(
466 &mut self,
467 data: &[u32],
468 ) -> Result<(), SFError> {
469 let atr = &mut self.attribute_pet_bonus;
470 *atr.get_mut(AttributeType::Constitution) = data.cget(1, "pet con")?;
473 *atr.get_mut(AttributeType::Dexterity) = data.cget(2, "pet dex")?;
474 *atr.get_mut(AttributeType::Intelligence) = data.cget(3, "pet int")?;
475 *atr.get_mut(AttributeType::Luck) = data.cget(4, "pet luck")?;
476 *atr.get_mut(AttributeType::Strength) = data.cget(5, "pet str")?;
477 Ok(())
478 }
479
480 pub(crate) fn update_fortress(
481 &mut self,
482 data: &[i64],
483 ) -> Result<(), SFError> {
484 let ft = self.fortress.get_or_insert_default();
485 ft.upgrade_count = data.csiget(0, "other ft upgrades", 0)?;
486 ft.fortifications_level =
487 data.csiget(1, "other soldier fortifications", 0)?;
488 ft.archer_count = data.csiget(2, "other mage count", 0)?;
489 ft.mage_count = data.csiget(3, "other soldier advice", 0)?;
490 ft.lootable_wood = data.csiget(4, "other lootable wood", 0)?;
491 ft.lootable_stone = data.csiget(5, "other lootable stone", 0)?;
492 Ok(())
493 }
494
495 pub(crate) fn update(
496 &mut self,
497 data: &[i64],
498 server_time: ServerTime,
499 ) -> Result<(), SFError> {
500 self.player_id = data.csiget(1, "player id", 0)?;
502 self.level = data.csimget(3, "level", 0, |a| a & 0xFFFF)?;
504 self.experience = data.csiget(4, "experience", 0)?;
505 self.next_level_xp = data.csiget(5, "xp to next lvl", 0)?;
506 self.honor = data.csiget(6, "honor", 0)?;
507 self.rank = data.csiget(7, "rank", 0)?;
508 self.portrait =
509 Portrait::parse(data.skip(8, "portrait")?).unwrap_or_default();
510 self.race = data.cfpuget(18, "char race", |a| a)?;
522 self.class = data.cfpuget(20, "character class", |a| a - 1)?;
525 self.mount = data.cfpget(21, "character mount", |a| a & 0xFF)?;
526 self.armor = data.csiget(23, "total armor", 0)?;
529 self.min_damage = data.csiget(24, "min damage", 0)?;
530 self.max_damage = data.csiget(25, "max damage", 0)?;
531 self.portal_dmg_bonus = data.cimget(26, "portal dmg bonus", |a| a)?;
532 self.portal_hp_bonus = data.csimget(28, "portal hp bonus", 0, |a| a)?;
534 self.mount_end = data.cstget(29, "mount end", server_time)?;
535 update_enum_map(
536 &mut self.attribute_basis,
537 data.skip(30, "char attr basis")?,
538 );
539 update_enum_map(
540 &mut self.attribute_additions,
541 data.skip(35, "char attr adds")?,
542 );
543 update_enum_map(
544 &mut self.attribute_times_bought,
545 data.skip(40, "char attr tb")?,
546 );
547 let sb_count = data.cget(66, "scrapbook count")?;
570 if sb_count >= 10000 {
571 self.scrapbook_count =
572 Some(soft_into(sb_count - 10000, "scrapbook count", 0));
573 }
574 self.gladiator_lvl = data.csiget(69, "gladiator lvl", 0)?;
577
578 Ok(())
579 }
580}
581
582#[derive(Debug, Clone, FromPrimitive)]
583#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
584pub enum CombatMessageType {
585 Arena = 0,
586 Quest = 1,
587 GuildFight = 2,
588 GuildRaid = 3,
589 Dungeon = 4,
590 TowerFight = 5,
591 LostFight = 6,
592 WonFight = 7,
593 FortressFight = 8,
594 FortressDefense = 9,
595 ShadowWorld = 12,
596 FortressDefenseAlreadyCountered = 109,
597 PetAttack = 14,
598 PetDefense = 15,
599 Underworld = 16,
600 Twister = 25,
601 GuildFightLost = 26,
602 GuildFightWon = 27,
603}
604
605#[derive(Debug, Clone, FromPrimitive)]
606#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
607pub enum MessageType {
608 Normal,
609 GuildInvite,
610 GuildKicked,
611}
612
613#[derive(Debug, Clone)]
614#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
615pub struct CombatLogEntry {
616 pub msg_id: i64,
617 pub player_name: String,
618 pub won: bool,
619 pub battle_type: CombatMessageType,
620 pub time: DateTime<Local>,
621}
622
623impl CombatLogEntry {
624 pub(crate) fn parse(
625 data: &[&str],
626 server_time: ServerTime,
627 ) -> Result<CombatLogEntry, SFError> {
628 let msg_id = data.cfsuget(0, "combat msg_id")?;
629 let battle_t: i64 = data.cfsuget(3, "battle t")?;
630 let time_stamp: i64 = data.cfsuget(4, "combat log time")?;
631 let time = server_time
632 .convert_to_local(time_stamp, "combat time")
633 .ok_or_else(|| {
634 SFError::ParsingError("combat time", time_stamp.to_string())
635 })?;
636
637 let mt = FromPrimitive::from_i64(battle_t).ok_or_else(|| {
638 SFError::ParsingError("combat mt", format!("{battle_t} @ {time:?}"))
639 })?;
640
641 Ok(CombatLogEntry {
642 msg_id,
643 player_name: data.cget(1, "clog player")?.to_string(),
644 won: data.cget(2, "clog won")? == "1",
645 battle_type: mt,
646 time,
647 })
648 }
649}
650
651#[derive(Debug, Clone)]
652#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
653pub struct InboxEntry {
654 pub msg_typ: MessageType,
655 pub from: String,
656 pub msg_id: i32,
657 pub title: String,
658 pub date: DateTime<Local>,
659 pub read: bool,
660}
661
662impl InboxEntry {
663 pub(crate) fn parse(
664 msg: &str,
665 server_time: ServerTime,
666 ) -> Result<InboxEntry, SFError> {
667 let parts = msg.splitn(4, ',').collect::<Vec<_>>();
668 let Some((title, date)) =
669 parts.cget(3, "msg title/date")?.rsplit_once(',')
670 else {
671 return Err(SFError::ParsingError(
672 "title/msg comma",
673 msg.to_string(),
674 ));
675 };
676
677 let msg_typ = match title {
678 "3" => MessageType::GuildKicked,
679 "5" => MessageType::GuildInvite,
680 x if x.chars().all(|a| a.is_ascii_digit()) => {
681 return Err(SFError::ParsingError(
682 "msg typ",
683 title.to_string(),
684 ));
685 }
686 _ => MessageType::Normal,
687 };
688
689 let Some(date) = date
690 .parse()
691 .ok()
692 .and_then(|a| server_time.convert_to_local(a, "msg_date"))
693 else {
694 return Err(SFError::ParsingError("msg date", date.to_string()));
695 };
696
697 Ok(InboxEntry {
698 msg_typ,
699 date,
700 from: parts.cget(1, "inbox from")?.to_string(),
701 msg_id: parts.cfsuget(0, "msg_id")?,
702 title: from_sf_string(title.trim_end_matches('\t')),
703 read: parts.cget(2, "inbox read")? == "1",
704 })
705 }
706}
707
708#[derive(Debug, Clone)]
709#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
710pub struct NewsEntry {
711 pub news_id: i64,
713 pub title: String,
715 pub date: DateTime<Local>,
717 pub read: bool,
719 }
721
722impl NewsEntry {
723 pub(crate) fn parse(
724 msg: &str,
725 server_time: ServerTime,
726 ) -> Result<NewsEntry, SFError> {
727 let parts = msg.splitn(4, ',').collect::<Vec<_>>();
728 let title = parts.cget(1, "news title")?;
729 let Ok(ts) = parts.cget(2, "news timestamp")?.parse::<i64>() else {
730 return Err(SFError::ParsingError("invalid news ts", msg.into()));
731 };
732
733 let Some(date) = server_time.convert_to_local(ts, "msg_date") else {
734 return Err(SFError::ParsingError("msg date", ts.to_string()));
735 };
736
737 Ok(NewsEntry {
738 news_id: ts,
739 date,
740 title: from_sf_string(title),
741 read: parts.cget(0, "inbox read")? == "1",
742 })
743 }
744}
745
746#[derive(Debug, Clone, Default)]
747#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
748pub struct OtherGuild {
749 pub name: String,
750
751 pub attacks: Option<String>,
752 pub defends_against: Option<String>,
753
754 pub rank: u16,
755 pub attack_cost: u32,
756 pub description: String,
757 pub emblem: Emblem,
758 pub honor: u32,
759 pub finished_raids: u16,
760 member_count: u8,
762 pub members: Vec<OtherGuildMember>,
763}
764
765#[derive(Debug, Clone, Default)]
766#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
767pub struct OtherGuildMember {
768 pub name: String,
769 pub instructor_lvl: u16,
770 pub treasure_lvl: u16,
771 pub rank: GuildRank,
772 pub level: u16,
773 pub pet_lvl: u16,
774 pub last_active: Option<DateTime<Local>>,
775}
776impl OtherGuild {
777 pub(crate) fn update(
778 &mut self,
779 val: &str,
780 server_time: ServerTime,
781 ) -> Result<(), SFError> {
782 let data: Vec<_> = val
783 .split('/')
784 .map(|c| c.trim().parse::<i64>().unwrap_or_default())
785 .collect();
786
787 self.member_count = data.csiget(3, "member count", 0)?;
788 let member_count = self.member_count as usize;
789 self.finished_raids = data.csiget(8, "raid count", 0)?;
790 self.honor = data.csiget(13, "other guild honor", 0)?;
791
792 self.members.resize_with(member_count, Default::default);
793
794 for (i, member) in &mut self.members.iter_mut().enumerate() {
795 member.level =
796 data.csiget(64 + i, "other guild member level", 0)?;
797 member.last_active =
798 data.cstget(114 + i, "other guild member active", server_time)?;
799 member.treasure_lvl =
800 data.csiget(214 + i, "other guild member treasure levels", 0)?;
801 member.instructor_lvl = data.csiget(
802 264 + i,
803 "other guild member instructor levels",
804 0,
805 )?;
806 member.rank = data
807 .cfpget(314 + i, "other guild member ranks", |q| q)?
808 .unwrap_or_default();
809 member.pet_lvl =
810 data.csiget(390 + i, "other guild pet levels", 0)?;
811 }
812 Ok(())
813 }
814}
815
816#[derive(Debug, Clone, Default)]
817#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
818pub struct RelationEntry {
819 pub id: PlayerId,
820 pub name: String,
821 pub guild: String,
822 pub level: u16,
823 pub relation: Relationship,
824}
825
826#[derive(Debug, Clone)]
827#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
828pub struct ClaimableMail {
829 pub msg_id: i64,
830 pub typ: ClaimableMailType,
831 pub status: ClaimableStatus,
832 pub name: String,
833 pub received: Option<DateTime<Local>>,
834 pub claimable_until: Option<DateTime<Local>>,
835}
836
837#[derive(Debug, Clone, PartialEq, Eq, Copy)]
838#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
839pub enum ClaimableStatus {
840 Unread,
841 Read,
842 Claimed,
843}
844
845#[derive(Debug, Clone, PartialEq, Eq, Default, FromPrimitive)]
846#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
847pub enum ClaimableMailType {
848 Coupon = 10,
849 SupermanDelivery = 11,
850 TwitchDrop = 12,
851 #[default]
852 GenericDelivery,
853}
854
855#[derive(Debug, Clone, Default)]
856#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
857pub struct ClaimablePreview {
858 pub items: Vec<Item>,
859 pub resources: Vec<Reward>,
860}