1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-07-23 13:07:45 +00:00

Implement more of the Content Finder

Now it can put you in the zone properly (just Satasha, really.)
The "checking member status" window doesn't go away yet.
This commit is contained in:
Joshua Goins 2025-07-18 16:53:40 -04:00
parent b608d32590
commit 60fcac80c2
6 changed files with 237 additions and 67 deletions

View file

@ -83,7 +83,7 @@ physis = { git = "https://github.com/redstrate/physis", default-features = false
bitflags = { version = "2.9", default-features = false }
# excel sheet data
icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.06.28.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate", "PlaceName", "GilShopItem"], default-features = false }
icarus = { git = "https://github.com/redstrate/Icarus", branch = "ver/2025.06.28.0000.0000", features = ["Warp", "Tribe", "ClassJob", "World", "TerritoryType", "Race", "Aetheryte", "EquipSlotCategory", "Action", "WeatherRate", "PlaceName", "GilShopItem", "InstanceContent", "ContentFinderCondition"], default-features = false }
# navimesh visualization
bevy = { version = "0.16", features = ["std",

View file

@ -251,14 +251,14 @@
"size": 16
},
{
"name": "ContentFinderFound",
"name": "ContentFinderUpdate",
"opcode": 619,
"size": 40
},
{
"name": "ContentFinderFound2",
"opcode": 619,
"size": 812
"name": "ContentFinderFound",
"opcode": 730,
"size": 40
},
{
"name": "ObjectSpawn",
@ -314,6 +314,11 @@
"name": "ItemObtainedLogMessage",
"opcode": 456,
"size": 22
},
{
"name": "ContentFinderCommencing",
"opcode": 485,
"size": 24
}
],
"ClientZoneIpcType": [
@ -471,6 +476,11 @@
"name": "ContentFinderRegister",
"opcode": 991,
"size": 40
},
{
"name": "ContentFinderAction",
"opcode": 409,
"size": 8
}
],
"ServerLobbyIpcType": [

View file

@ -1147,64 +1147,197 @@ async fn client_loop(
}
ClientZoneIpcData::ContentFinderRegister { content_ids, .. } => {
tracing::info!("Searching for {content_ids:?}");
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ContentFinderFound,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ContentFinderFound {
state1: 2,
classjob_id: 1,
unk1: [
5,
2,
5,
2,
5,
2,
96,
4,
5,
64,
2,
5,
2,
5,
2,
2,
2,
2,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
},
..Default::default()
};
connection
.send_segment(PacketSegment {
source_actor: connection.player_data.actor_id,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc,
data: SegmentData::Ipc { data: ipc },
})
.await;
connection.queued_content = Some(content_ids[0]);
// update
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ContentFinderUpdate,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ContentFinderUpdate {
state1: 1,
classjob_id: connection.player_data.classjob_id, // TODO: store what they registered with, because it can change
unk1: [
0,
0,
0,
0,
0,
0,
96,
4,
2,
64,
1,
0,
0,
0,
0,
0,
1,
1,
],
content_ids: [
4,
0,
0,
0,
0,
],
unk2: [
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
],
},
..Default::default()
};
connection
.send_segment(PacketSegment {
source_actor: connection.player_data.actor_id,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc,
data: SegmentData::Ipc { data: ipc },
})
.await;
}
// found
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ContentFinderFound,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ContentFinderFound {
unk1: [
3,
0,
0,
0,
0,
0,
0,
0,
96,
4,
2,
64,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
],
},
..Default::default()
};
connection
.send_segment(PacketSegment {
source_actor: connection.player_data.actor_id,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc,
data: SegmentData::Ipc { data: ipc },
})
.await;
}
}
ClientZoneIpcData::ContentFinderAction { unk1 } => {
dbg!(unk1);
// commencing
{
let ipc = ServerZoneIpcSegment {
op_code: ServerZoneIpcType::ContentFinderCommencing,
timestamp: timestamp_secs(),
data: ServerZoneIpcData::ContentFinderCommencing {
unk1: [
4,
0,
0,
0,
1,
0,
0,
0,
4,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
0,
0,
0,
],
},
..Default::default()
};
connection
.send_segment(PacketSegment {
source_actor: connection.player_data.actor_id,
target_actor: connection.player_data.actor_id,
segment_type: SegmentType::Ipc,
data: SegmentData::Ipc { data: ipc },
})
.await;
}
// TODO: content finder should be moved to global state
// For now, just send them to do the zone if they do anything
let zone_id;
{
let mut game_data = game_data.lock().unwrap();
zone_id = game_data.find_zone_for_content(connection.queued_content.unwrap());
}
if let Some(zone_id) = zone_id {
connection.change_zone(zone_id).await;
} else {
tracing::warn!("Failed to find zone id for content?!");
}
connection.queued_content = None;
}
ClientZoneIpcData::Unknown { .. } => {
tracing::warn!("Unknown packet {:?} recieved, this should be handled!", data.op_code);
@ -1376,6 +1509,7 @@ async fn main() {
gracefully_logged_out: false,
weather_id: 0,
obsfucation_data: ObsfucationData::default(),
queued_content: None,
});
}
Some((mut socket, _)) = handle_rcon(&rcon_listener) => {

View file

@ -3,8 +3,10 @@ use std::path::PathBuf;
use icarus::Action::ActionSheet;
use icarus::Aetheryte::AetheryteSheet;
use icarus::ClassJob::ClassJobSheet;
use icarus::ContentFinderCondition::ContentFinderConditionSheet;
use icarus::EquipSlotCategory::EquipSlotCategorySheet;
use icarus::GilShopItem::GilShopItemSheet;
use icarus::InstanceContent::InstanceContentSheet;
use icarus::PlaceName::PlaceNameSheet;
use icarus::TerritoryType::TerritoryTypeSheet;
use icarus::WeatherRate::WeatherRateSheet;
@ -437,6 +439,20 @@ impl GameData {
self.get_item_info(ItemInfoQuery::ById(*item_id as u32))
}
/// Gets the zone id for the given InstanceContent.
pub fn find_zone_for_content(&mut self, content_id: u16) -> Option<u16> {
let instance_content_sheet =
InstanceContentSheet::read_from(&mut self.resource, Language::None).unwrap();
let instance_content_row = instance_content_sheet.get_row(content_id as u32)?;
let content_finder_row_id = instance_content_row.ContentFinderCondition().into_u16()?;
let content_finder_sheet =
ContentFinderConditionSheet::read_from(&mut self.resource, Language::English).unwrap();
let content_finder_row = content_finder_sheet.get_row(*content_finder_row_id as u32)?;
content_finder_row.TerritoryType().into_u16().copied()
}
}
// Simple enum for GameData::get_territory_name

View file

@ -459,8 +459,8 @@ pub enum ServerZoneIpcData {
/// Unknown, seems to always be 0x00000200.
unk2: u32,
},
#[br(pre_assert(*magic == ServerZoneIpcType::ContentFinderFound))]
ContentFinderFound {
#[br(pre_assert(*magic == ServerZoneIpcType::ContentFinderUpdate))]
ContentFinderUpdate {
/// 0 = Nothing happens
/// 1 = Reserving server
/// 2 = again? ^
@ -469,10 +469,12 @@ pub enum ServerZoneIpcData {
/// nothing appears to happen above 5
state1: u8,
classjob_id: u8,
unk1: [u8; 38],
unk1: [u8; 18],
content_ids: [u16; 5],
unk2: [u8; 10],
},
#[br(pre_assert(*magic == ServerZoneIpcType::ContentFinderFound2))]
ContentFinderFound2 { unk1: [u8; 8] },
#[br(pre_assert(*magic == ServerZoneIpcType::ContentFinderFound))]
ContentFinderFound { unk1: [u8; 40] },
#[br(pre_assert(*magic == ServerZoneIpcType::ObjectSpawn))]
ObjectSpawn(ObjectSpawn),
#[br(pre_assert(*magic == ServerZoneIpcType::ActorGauge))]
@ -554,6 +556,9 @@ pub enum ServerZoneIpcData {
},
#[br(pre_assert(*magic == ServerZoneIpcType::EffectResult))]
EffectResult(EffectResult),
/// Sent to give you the green checkmark before entering a CF zone.
#[br(pre_assert(*magic == ServerZoneIpcType::ContentFinderCommencing))]
ContentFinderCommencing { unk1: [u8; 24] },
Unknown {
#[br(count = size - 32)]
unk: Vec<u8>,
@ -750,6 +755,8 @@ pub enum ClientZoneIpcData {
#[brw(pad_after = 4)] // seems to empty
content_ids: [u16; 5],
},
#[br(pre_assert(*magic == ClientZoneIpcType::ContentFinderAction))]
ContentFinderAction { unk1: [u8; 8] },
Unknown {
#[br(count = size - 32)]
unk: Vec<u8>,

View file

@ -139,6 +139,9 @@ pub struct ZoneConnection {
pub weather_id: u16,
pub obsfucation_data: ObsfucationData,
// TODO: support more than one content in the queue
pub queued_content: Option<u16>,
}
impl ZoneConnection {