Browse Source

load quests on room create based on room mode

pull/86/head
andy 3 years ago
parent
commit
20625887c6
  1. 39
      data/quests.toml
  2. 59
      src/ship/packet/handler/quest.rs
  3. 17
      src/ship/quests.rs
  4. 20
      src/ship/room.rs
  5. 18
      src/ship/ship.rs

39
data/quests.toml

@ -1,39 +0,0 @@
[Extermination]
list_order = 1
description = "I am a description"
[[Extermination.quests]]
bin = "q058-ret-bb.bin"
dat = "q058-ret-bb.dat"
[[Extermination.quests]]
bin = "q059-ret-bb.bin"
dat = "q059-ret-bb.dat"
[Retrieval]
list_order = 2
description = "find some shit"
[[Retrieval.quests]]
bin = "q101-ext-bb.bin"
dat = "q101-ext-bb.dat"
[[Retrieval.quests]]
bin = "q102-ext-bb.bin"
dat = "q102-ext-bb.dat"
#drop_table = "q102-drops"
[[Retrieval.quests]]
bin = "q233-ext-bb.bin"
dat = "q233-ext-bb.dat"
#drop_table = "q102-drops"
[[Retrieval.quests]]
bin = "q236-ext-bb.bin"
dat = "q236-ext-bb.dat"
#drop_table = "q102-drops"
[[Retrieval.quests]]
bin = "q118-vr-bb.bin"
dat = "q118-vr-bb.dat"

59
src/ship/packet/handler/quest.rs

@ -2,7 +2,6 @@ use std::io::{Cursor, Read, Seek, SeekFrom};
use libpso::packet::ship::*; use libpso::packet::ship::*;
use crate::common::serverstate::ClientId; use crate::common::serverstate::ClientId;
use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms}; use crate::ship::ship::{SendShipPacket, ShipError, Clients, Rooms};
use crate::ship::quests::QuestList;
use crate::ship::location::{ClientLocation, ClientLocationError}; use crate::ship::location::{ClientLocation, ClientLocationError};
use crate::ship::packet::builder::quest; use crate::ship::packet::builder::quest;
use libpso::util::array_to_utf8; use libpso::util::array_to_utf8;
@ -37,24 +36,38 @@ fn parse_filename(filename_bytes: &[u8; 16]) -> Result<(u16, u16, QuestFileType)
} }
pub fn send_quest_category_list(id: ClientId, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let qcl = quest::quest_category_list(quests);
pub fn send_quest_category_list(id: ClientId, client_location: &ClientLocation, rooms: &mut Rooms) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let qcl = quest::quest_category_list(&room.quests);
Ok(Box::new(vec![(id, SendShipPacket::QuestCategoryList(qcl))].into_iter())) Ok(Box::new(vec![(id, SendShipPacket::QuestCategoryList(qcl))].into_iter()))
} }
pub fn select_quest_category(id: ClientId, menuselect: &MenuSelect, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
pub fn select_quest_category(id: ClientId, menuselect: &MenuSelect, client_location: &ClientLocation, rooms: &mut Rooms) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let (_, category_quests) = room.quests.iter()
.nth(menuselect.item as usize) .nth(menuselect.item as usize)
.ok_or(ShipError::InvalidQuestCategory(menuselect.item))?; .ok_or(ShipError::InvalidQuestCategory(menuselect.item))?;
let ql = quest::quest_list(menuselect.item, category_quests); let ql = quest::quest_list(menuselect.item, category_quests);
for q in ql.quests.clone() {
println!("name: {:?} quest_id: {}", q.name, q.quest_id);
}
Ok(Box::new(vec![(id, SendShipPacket::QuestOptionList(ql))].into_iter())) Ok(Box::new(vec![(id, SendShipPacket::QuestOptionList(ql))].into_iter()))
} }
pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, client_location: &ClientLocation, rooms: &mut Rooms) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let (_, category_quests) = room.quests.iter()
.nth(questdetailrequest.category as usize) .nth(questdetailrequest.category as usize)
.ok_or(ShipError::InvalidQuestCategory(questdetailrequest.category as u32))?; .ok_or(ShipError::InvalidQuestCategory(questdetailrequest.category as u32))?;
@ -68,9 +81,13 @@ pub fn quest_detail(id: ClientId, questdetailrequest: &QuestDetailRequest, quest
Ok(Box::new(vec![(id, SendShipPacket::QuestDetail(qd))].into_iter())) Ok(Box::new(vec![(id, SendShipPacket::QuestDetail(qd))].into_iter()))
} }
pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quests: &QuestList, clients: &mut Clients, client_location: &ClientLocation, rooms: &mut Rooms)
pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, clients: &mut Clients, client_location: &ClientLocation, rooms: &mut Rooms)
-> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> { -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let (_, category_quests) = quests.iter()
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let (_, category_quests) = room.quests.iter()
.nth(questmenuselect.category as usize) .nth(questmenuselect.category as usize)
.ok_or(ShipError::InvalidQuestCategory(questmenuselect.category as u32))?; .ok_or(ShipError::InvalidQuestCategory(questmenuselect.category as u32))?;
@ -79,10 +96,6 @@ pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quest
q.id == questmenuselect.quest as u16 q.id == questmenuselect.quest as u16
}).ok_or(ShipError::InvalidQuest(questmenuselect.quest as u32))?; }).ok_or(ShipError::InvalidQuest(questmenuselect.quest as u32))?;
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &room.rare_monster_table); room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &room.rare_monster_table);
room.map_areas = quest.map_areas.clone(); room.map_areas = quest.map_areas.clone();
@ -100,9 +113,14 @@ pub fn player_chose_quest(id: ClientId, questmenuselect: &QuestMenuSelect, quest
}))) })))
} }
pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, client_location: &ClientLocation, rooms: &mut Rooms) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?; let (category_id, quest_id, datatype) = parse_filename(&quest_file_request.filename)?;
let (_, category_quests) = quests.iter()
let (_, category_quests) = room.quests.iter()
.nth(category_id as usize) .nth(category_id as usize)
.ok_or(ShipError::InvalidQuestCategory(category_id as u32))?; .ok_or(ShipError::InvalidQuestCategory(category_id as u32))?;
@ -124,9 +142,14 @@ pub fn quest_file_request(id: ClientId, quest_file_request: &QuestFileRequest, q
Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter())) Ok(Box::new(vec![(id, SendShipPacket::QuestChunk(qc))].into_iter()))
} }
pub fn quest_chunk_ack(id: ClientId, quest_chunk_ack: &QuestChunkAck, quests: &QuestList) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
pub fn quest_chunk_ack(id: ClientId, quest_chunk_ack: &QuestChunkAck, client_location: &ClientLocation, rooms: &mut Rooms) -> Result<Box<dyn Iterator<Item = (ClientId, SendShipPacket)> + Send>, ShipError> {
let room_id = client_location.get_room(id).map_err(|err| -> ClientLocationError { err.into() })?;
let room = rooms.get_mut(room_id.0)
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?.as_mut()
.ok_or(ShipError::InvalidRoom(room_id.0 as u32))?;
let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?; let (category_id, quest_id, datatype) = parse_filename(&quest_chunk_ack.filename)?;
let (_, category_quests) = quests.iter()
let (_, category_quests) = room.quests.iter()
.nth(category_id as usize) .nth(category_id as usize)
.ok_or(ShipError::InvalidQuestCategory(category_id as u32))?; .ok_or(ShipError::InvalidQuestCategory(category_id as u32))?;

17
src/ship/quests.rs

@ -208,13 +208,12 @@ impl Quest {
// QuestCollection // QuestCollection
pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>; pub type QuestList = BTreeMap<QuestCategory, Vec<Quest>>;
pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf) -> Option<Quest> {
let dat_file = File::open(PathBuf::from("data/quests/").join(dat_path.clone()))
pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf, quest_path: PathBuf) -> Option<Quest> {
let dat_file = File::open(quest_path.join(dat_path.clone()))
.map_err(|err| { .map_err(|err| {
warn!("could not load quest file {:?}: {:?}", dat_path, err) warn!("could not load quest file {:?}: {:?}", dat_path, err)
}).ok()?; }).ok()?;
//let bin_file = File::open(format!("data/quests/{}", bin_path))
let bin_file = File::open(PathBuf::from("data/quests/").join(bin_path.clone()))
let bin_file = File::open(quest_path.join(bin_path.clone()))
.map_err(|err| { .map_err(|err| {
warn!("could not load quest file {:?}: {:?}", bin_path, err) warn!("could not load quest file {:?}: {:?}", bin_path, err)
}).ok()?; }).ok()?;
@ -233,11 +232,11 @@ pub fn load_quest(bin_path: PathBuf, dat_path: PathBuf) -> Option<Quest> {
} }
pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
let mut f = File::open(quest_path).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
pub fn load_quests(quest_path: &mut PathBuf) -> Result<QuestList, QuestLoadError> {
let mut f = File::open(quest_path.clone()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
let mut s = String::new(); let mut s = String::new();
f.read_to_string(&mut s)?; f.read_to_string(&mut s)?;
quest_path.pop(); // remove quests.toml from the path
let mut used_quest_ids = BTreeSet::new(); let mut used_quest_ids = BTreeSet::new();
let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?; let ql: BTreeMap<String, QuestListCategory> = toml::from_str(s.as_str()).map_err(|_| QuestLoadError::CouldNotLoadConfigFile)?;
@ -245,7 +244,7 @@ pub fn load_quests(quest_path: PathBuf) -> Result<QuestList, QuestLoadError> {
let quests = category_details.quests let quests = category_details.quests
.into_iter() .into_iter()
.filter_map(|quest| { .filter_map(|quest| {
load_quest(quest.bin.into(), quest.dat.into())
load_quest(quest.bin.into(), quest.dat.into(), quest_path.to_path_buf())
.and_then(|quest | { .and_then(|quest | {
if used_quest_ids.contains(&quest.id) { if used_quest_ids.contains(&quest.id) {
warn!("quest id already exists: {}", quest.id); warn!("quest id already exists: {}", quest.id);
@ -278,7 +277,7 @@ mod test {
// one of the other maps to be a second tower // one of the other maps to be a second tower
#[test] #[test]
fn test_quest_with_remapped_floors() { fn test_quest_with_remapped_floors() {
let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into()).unwrap();
let pw4 = load_quest("q236-ext-bb.bin".into(), "q236-ext-bb.dat".into(), "data/quests/bb/ep2/multi".into()).unwrap();
let enemies_not_in_tower = pw4.enemies.iter() let enemies_not_in_tower = pw4.enemies.iter()
.filter(|enemy| { .filter(|enemy| {
enemy.is_some() enemy.is_some()

20
src/ship/room.rs

@ -10,6 +10,9 @@ use crate::entity::character::SectionID;
use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats}; use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
use crate::ship::map::area::MapAreaLookup; use crate::ship::map::area::MapAreaLookup;
use crate::ship::map::enemy::RareMonsterAppearTable; use crate::ship::map::enemy::RareMonsterAppearTable;
use crate::ship::quests;
use std::path::PathBuf;
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("")] #[error("")]
@ -18,6 +21,7 @@ pub enum RoomCreationError {
InvalidEpisode(u8), InvalidEpisode(u8),
InvalidDifficulty(u8), InvalidDifficulty(u8),
CouldNotLoadMonsterStats(RoomMode), CouldNotLoadMonsterStats(RoomMode),
CouldNotLoadQuests,
} }
#[derive(Debug, Copy, Clone, derive_more::Display)] #[derive(Debug, Copy, Clone, derive_more::Display)]
@ -97,19 +101,23 @@ impl From<Difficulty> for u8 {
} }
} }
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, derive_more::Display)]
pub enum RoomMode { pub enum RoomMode {
#[display(fmt="single")]
Single { Single {
episode: Episode, episode: Episode,
difficulty: Difficulty, difficulty: Difficulty,
}, },
#[display(fmt="multi")]
Multi { Multi {
episode: Episode, episode: Episode,
difficulty: Difficulty, difficulty: Difficulty,
}, },
#[display(fmt="challenge")]
Challenge { Challenge {
episode: Episode, episode: Episode,
}, },
#[display(fmt="battle")]
Battle { Battle {
episode: Episode, episode: Episode,
difficulty: Difficulty, difficulty: Difficulty,
@ -171,6 +179,7 @@ pub struct RoomState {
pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>, pub monster_stats: Box<HashMap<MonsterType, MonsterStats>>,
pub map_areas: MapAreaLookup, pub map_areas: MapAreaLookup,
pub rare_monster_table: Box<RareMonsterAppearTable>, pub rare_monster_table: Box<RareMonsterAppearTable>,
pub quests: quests::QuestList,
// items on ground // items on ground
// enemy info // enemy info
} }
@ -236,6 +245,14 @@ impl RoomState {
}; };
let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode()); let rare_monster_table = RareMonsterAppearTable::new(room_mode.episode());
let mut qpath = PathBuf::from("data/quests/bb");
qpath.push(room_mode.episode().to_string());
qpath.push(room_mode.to_string());
qpath.push("quests.toml");
let room_quests = match quests::load_quests(&mut qpath) {
Ok(qlist) => qlist,
Err(_) => return Err(RoomCreationError::CouldNotLoadQuests),
};
Ok(RoomState { Ok(RoomState {
monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?), monster_stats: Box::new(load_monster_stats_table(&room_mode).map_err(|_| RoomCreationError::CouldNotLoadMonsterStats(room_mode))?),
@ -249,6 +266,7 @@ impl RoomState {
drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)), drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
bursting: false, bursting: false,
map_areas: MapAreaLookup::new(&room_mode.episode()), map_areas: MapAreaLookup::new(&room_mode.episode()),
quests: room_quests,
}) })
} }
} }

18
src/ship/ship.rs

@ -405,7 +405,6 @@ impl<EG: EntityGateway> ShipServerStateBuilder<EG> {
level_table: CharacterLevelTable::default(), level_table: CharacterLevelTable::default(),
name: self.name.unwrap_or_else(|| "NAMENOTSET".into()), name: self.name.unwrap_or_else(|| "NAMENOTSET".into()),
item_manager: items::ItemManager::default(), item_manager: items::ItemManager::default(),
quests: quests::load_quests("data/quests.toml".into()).unwrap(),
ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)), ip: self.ip.unwrap_or_else(|| Ipv4Addr::new(127,0,0,1)),
port: self.port.unwrap_or(SHIP_PORT), port: self.port.unwrap_or(SHIP_PORT),
shops: Box::new(ItemShops::default()), shops: Box::new(ItemShops::default()),
@ -450,7 +449,6 @@ pub struct ShipServerState<EG: EntityGateway> {
level_table: CharacterLevelTable, level_table: CharacterLevelTable,
name: String, name: String,
item_manager: items::ItemManager, item_manager: items::ItemManager,
quests: quests::QuestList,
shops: Box<ItemShops>, shops: Box<ItemShops>,
pub blocks: Blocks, pub blocks: Blocks,
@ -611,8 +609,9 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
.await?.into_iter().map(move |pkt| (id, pkt))) .await?.into_iter().map(move |pkt| (id, pkt)))
}, },
RecvShipPacket::QuestDetailRequest(questdetailrequest) => { RecvShipPacket::QuestDetailRequest(questdetailrequest) => {
let block = self.blocks.with_client(id, &self.clients)?;
match questdetailrequest.menu { match questdetailrequest.menu {
QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &self.quests)?,
QUEST_SELECT_MENU_ID => handler::quest::quest_detail(id, questdetailrequest, &block.client_location, &mut block.rooms)?,
_ => unreachable!(), _ => unreachable!(),
} }
}, },
@ -630,13 +629,13 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
Box::new(leave_lobby.chain(select_block)) Box::new(leave_lobby.chain(select_block))
} }
ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?, ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &mut self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms)?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &self.quests)?,
QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &mut block.rooms)?,
_ => unreachable!(), _ => unreachable!(),
} }
}, },
RecvShipPacket::QuestMenuSelect(questmenuselect) => { RecvShipPacket::QuestMenuSelect(questmenuselect) => {
let block = self.blocks.with_client(id, &self.clients)?; let block = self.blocks.with_client(id, &self.clients)?;
handler::quest::player_chose_quest(id, questmenuselect, &self.quests, &mut self.clients, &block.client_location, &mut block.rooms)?
handler::quest::player_chose_quest(id, questmenuselect, &mut self.clients, &block.client_location, &mut block.rooms)?
}, },
RecvShipPacket::MenuDetail(_menudetail) => { RecvShipPacket::MenuDetail(_menudetail) => {
//unreachable!(); //unreachable!();
@ -714,13 +713,16 @@ impl<EG: EntityGateway> ServerState for ShipServerState<EG> {
Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter()) Box::new(handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_manager, &self.level_table, &mut block.rooms, &mut self.entity_gateway).await?.into_iter())
}, },
RecvShipPacket::RequestQuestList(_) => { RecvShipPacket::RequestQuestList(_) => {
handler::quest::send_quest_category_list(id, &self.quests)?
let block = self.blocks.with_client(id, &self.clients)?;
handler::quest::send_quest_category_list(id, &block.client_location, &mut block.rooms)?
}, },
RecvShipPacket::QuestFileRequest(quest_file_request) => { RecvShipPacket::QuestFileRequest(quest_file_request) => {
handler::quest::quest_file_request(id, quest_file_request, &self.quests)?
let block = self.blocks.with_client(id, &self.clients)?;
handler::quest::quest_file_request(id, quest_file_request, &block.client_location, &mut block.rooms)?
}, },
RecvShipPacket::QuestChunkAck(quest_chunk_ack) => { RecvShipPacket::QuestChunkAck(quest_chunk_ack) => {
handler::quest::quest_chunk_ack(id, quest_chunk_ack, &self.quests)?
let block = self.blocks.with_client(id, &self.clients)?;
handler::quest::quest_chunk_ack(id, quest_chunk_ack, &block.client_location, &mut block.rooms)?
}, },
RecvShipPacket::DoneLoadingQuest(_) => { RecvShipPacket::DoneLoadingQuest(_) => {
let block = self.blocks.with_client(id, &self.clients)?; let block = self.blocks.with_client(id, &self.clients)?;

Loading…
Cancel
Save