diff --git a/core/src/ipc/zone/client/client_trigger.rs b/core/src/ipc/zone/client/client_trigger.rs index 24486fb2..18bf4221 100644 --- a/core/src/ipc/zone/client/client_trigger.rs +++ b/core/src/ipc/zone/client/client_trigger.rs @@ -295,6 +295,10 @@ pub enum ClientTriggerCommand { ward_index: u32, }, + /// The client requests to warp to the housing interior's front door. + #[brw(magic = 1122u32)] + HousingMoveToFrontDoor {}, + /// The client has requested an apartment building's residents list from either an apartment building entrance or an apartment's front door. #[brw(magic = 1125u32)] RequestApartmentList { diff --git a/core/src/ipc/zone/server/housing_interior_furniture.rs b/core/src/ipc/zone/server/housing_interior_furniture.rs new file mode 100644 index 00000000..c4b616d9 --- /dev/null +++ b/core/src/ipc/zone/server/housing_interior_furniture.rs @@ -0,0 +1,78 @@ +use crate::common::{LandId, Position}; +use binrw::binrw; + +#[binrw] +#[derive(Clone, Debug, Default)] +pub struct FurnitureList { + /// The LandId this list is for. + pub land_id: LandId, + pub unk1: u8, + /// The current `index` out of `count` packets to be sent. + pub index: u8, + /// The number of these lists that will be sent. + pub count: u8, + /// Seems to be some sort of outdoor vs indoor flag. If it's 0, it means this is for outdoor furniture, and 100 means it's for an interior. + pub unk2: u8, + /// The actual furnishings. + #[br(count = Furniture::COUNT)] + #[brw(pad_size_to = Furniture::COUNT * Furniture::SIZE)] + #[brw(pad_after = 4)] // Seems to be empty/zeroes + pub furniture: Vec, +} + +#[binrw] +#[derive(Copy, Clone, Debug, Default)] +pub struct HousingInteriorDetails { + /// This interior's window style. + pub window_style: u16, + pub unk1: u16, // Sapphire calls this "window color", but windows cannot be dyed? + /// This interior's door style. + pub door_style: u16, + /// This interior door's dye colour. Index into the Stain Excel sheet. + pub door_stain: u16, + /// The light level in the interior. Note that this is actually described in terms of a level of *darkness*. When the client sets the UI light level to 5, the client will send value 0 in the ClientTrigger. Other examples: light level 1 will send 4, light level 2 will send 3, and so on. + pub light_level: u8, + pub unk2: [u8; 3], // likely just padding + /// The ground floor's wall style. In an apartment, this along with ground_floor and ground_chandelier dictate what will decorate the apartment, leaving doors, windows, top floor and cellar all zeroes/blank. + // TODO: It's unclear if these are pairs of u16s or just u32s. + // NOTE: Be careful when experimenting with these values, as invalid combinations of u16s can crash the client, particularly if the interior is an apartment and top floor or cellar values are changed! + pub ground_walls: u32, + /// The ground floor's style/texture. + pub ground_floor: u32, + /// THe ground floor's chandelier. Unknown if this is a model id + toggle, or an item id + toggle. + pub ground_chandelier: u32, + /// The top floor's wall style/texture. + pub top_walls: u32, + /// The top floor's style/texture. + pub top_floor: u32, + /// The top floor's chandelier. + pub top_chandelier: u32, + /// The cellar's wall style/texture. + pub cellar_walls: u32, + /// The cellar's floor syle/texture. + pub cellar_floor: u32, + /// The cellar's chandelier. + pub cellar_chandelier: u32, + pub unk_interior: u32, // Unclear what this is, it can have data in mansions but not apartments or medium houses? + unk3: u32, // Might just be padding, seen as zeroes so far +} + +#[binrw] +#[derive(Copy, Clone, Debug, Default)] +pub struct Furniture { + /// Index into the FurnitureCatalogItemList sheet. If 0, no item is present in this entry. Therefore, this index needs to subtract 1 when indexing into the sheet! + pub catalog_id: u16, + pub unk1: u16, // Seems to always be 1 when this item is present. + /// Index into the Stain sheet. Sets the dye for this item. + pub stain: u16, + pub unk2: [u8; 2], // Likely padding, but unsure. + /// This item's rotation. + pub rotation: f32, + /// This item's 3d coordinates in the housing interior. + pub position: Position, +} + +impl Furniture { + pub const SIZE: usize = 24; + pub const COUNT: usize = 100; +} diff --git a/core/src/ipc/zone/server/mod.rs b/core/src/ipc/zone/server/mod.rs index afa50875..bd741da1 100644 --- a/core/src/ipc/zone/server/mod.rs +++ b/core/src/ipc/zone/server/mod.rs @@ -116,6 +116,9 @@ pub use house_list::{House, HouseList}; mod housing_ward; pub use housing_ward::{HousingWardInfo, HousingWardSummaryItem}; +mod housing_interior_furniture; +pub use housing_interior_furniture::{Furniture, FurnitureList, HousingInteriorDetails}; + mod housing_occupied_land_info; pub use housing_occupied_land_info::HousingOccupiedLandInfo; @@ -850,12 +853,7 @@ pub enum ServerZoneIpcData { index: u8, unk2: [u8; 7], // all padding }, - UnkHousingRelated { - unk1: [u8; 9], - index: u8, - count: u8, - unk2: [u8; 2135], - }, + FurnitureList(FurnitureList), OwnedHousing { #[brw(pad_after = 8)] // believe these are always empty? unk1: LandData, @@ -1282,9 +1280,7 @@ pub enum ServerZoneIpcData { #[brw(pad_after = 16)] // Seems to be empty/zeroes unk: u32, }, - UnkHousingRelated2 { - unk: [u8; 56], - }, + HousingInteriorDetails(HousingInteriorDetails), ApartmentList(ApartmentList), FriendRemoved { #[brw(pad_after = 4)] // Seems to be empty/zeroes diff --git a/resources/data/opcodes.yml b/resources/data/opcodes.yml index 1663d004..fbae532c 100644 --- a/resources/data/opcodes.yml +++ b/resources/data/opcodes.yml @@ -495,10 +495,10 @@ ServerZoneIpcType: comment: Updates a map effect. opcode: 746 size: 16 - - name: UnkHousingRelated - comment: Unknown purpose. + - name: FurnitureList + comment: Sent by the server to inform the client about furniture (both indoor & outdoor) that exists in this ward or housing interior. opcode: 376 - size: 2146 + size: 2416 - name: OwnedHousing comment: Sent to inform you of which housing you own/have access to. opcode: 404 @@ -675,8 +675,8 @@ ServerZoneIpcType: comment: The server sends the client an error message due to something going wrong with a linkshell. opcode: 190 size: 24 - - name: UnkHousingRelated2 - comment: The server sends the client currently unknown information about the housing interior they're entering. + - name: HousingInteriorDetails + comment: The server sends the client exhaustive information (wall/floor textures, window models, etc.) about the housing interior they entered. opcode: 389 size: 56 - name: ApartmentList diff --git a/servers/world/src/zone_connection/zone.rs b/servers/world/src/zone_connection/zone.rs index 9cd97976..1f4d9892 100644 --- a/servers/world/src/zone_connection/zone.rs +++ b/servers/world/src/zone_connection/zone.rs @@ -6,12 +6,13 @@ use crate::{ lua::{LuaContent, LuaZone}, }; use kawari::{ - common::{HandlerId, HandlerType, Position, timestamp_secs}, + common::{HandlerId, HandlerType, LandId, Position, timestamp_secs}, config::get_config, constants::OBFUSCATION_ENABLED_MODE, ipc::zone::{ - ActorControlCategory, Condition, ContentRegistrationFlags, House, HouseList, InitZone, - InitZoneFlags, ServerZoneIpcData, ServerZoneIpcSegment, WarpType, WeatherChange, + ActorControlCategory, Condition, ContentRegistrationFlags, FurnitureList, House, HouseList, + HousingInteriorDetails, InitZone, InitZoneFlags, ServerZoneIpcData, ServerZoneIpcSegment, + WarpType, WeatherChange, }, packet::{ConnectionState, PacketSegment, ScramblerKeyGenerator, SegmentData, SegmentType}, }; @@ -218,33 +219,40 @@ impl ZoneConnection { .await; for index in 0..8 { - self.send_ipc_self(ServerZoneIpcSegment::new( - ServerZoneIpcData::UnkHousingRelated { - unk1: [0xFF; 9], - index, + self.send_ipc_self(ServerZoneIpcSegment::new(ServerZoneIpcData::FurnitureList( + FurnitureList { count: 8, - unk2: [0; 2135], + index, + ..Default::default() }, - )) + ))) .await; } } if lua_zone.intended_use == TerritoryIntendedUse::HousingIndoor as u8 { - // Bare minimum stuff to make housing interiors load, hardly anything is known about these for now + let config = get_config(); + // Bare minimum stuff to make housing interiors load self.send_ipc_self(ServerZoneIpcSegment::new( - ServerZoneIpcData::UnkHousingRelated2 { unk: [0; 56] }, + ServerZoneIpcData::HousingInteriorDetails(HousingInteriorDetails::default()), )) .await; - self.send_ipc_self(ServerZoneIpcSegment::new( - ServerZoneIpcData::UnkHousingRelated { - unk1: [0; 9], // Slight hack for now: these need to be something other than 0xFF or housing items can't spawn via plugins - index: 0, + // The LandId is currently set so that plugins like HousingPos/Buildingway can plop stuff down + self.send_ipc_self(ServerZoneIpcSegment::new(ServerZoneIpcData::FurnitureList( + FurnitureList { + land_id: LandId { + id: 128, + ward: 1, + territory_type_id: lua_zone.zone_id, + world_id: config.world.world_id, + }, count: 1, - unk2: [0xFF; 2135], // Slight hack for now: if it's all zeroes, plugins like housingpos won't be able to arbitrarily spawn client-side-only housing items + index: 0, + unk2: 100, // Indoors + ..Default::default() }, - )) + ))) .await; }