diff --git a/Cargo.lock b/Cargo.lock
index 8b5c4c5..f343af7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -609,7 +609,6 @@ dependencies = [
  "derive_more",
  "enum-utils",
  "fern",
- "fix-hidden-lifetime-bug",
  "futures",
  "lazy_static",
  "libpso",
@@ -692,26 +691,6 @@ dependencies = [
  "log",
 ]
 
-[[package]]
-name = "fix-hidden-lifetime-bug"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4ae9c2016a663983d4e40a9ff967d6dcac59819672f0b47f2b17574e99c33c8"
-dependencies = [
- "fix-hidden-lifetime-bug-proc_macros",
-]
-
-[[package]]
-name = "fix-hidden-lifetime-bug-proc_macros"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4c81935e123ab0741c4c4f0d9b8377e5fb21d3de7e062fa4b1263b1fbcba1ea"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
 [[package]]
 name = "foreign-types"
 version = "0.3.2"
diff --git a/Cargo.toml b/Cargo.toml
index 391e99a..e746d70 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,4 +33,3 @@ sqlx = { version = "0.5.10", features = ["runtime-async-std-native-tls", "postgr
 strum = "0.19.5"
 strum_macros = "0.19"
 anyhow = { version = "1.0.47", features = ["backtrace"] }
-fix-hidden-lifetime-bug = "0.2.4"
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 3195373..eb7c687 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -5,7 +5,7 @@ use elseware::common::interserver::AuthToken;
 use elseware::login::login::LoginServerState;
 use elseware::login::character::CharacterServerState;
 use elseware::patch::patch::{PatchServerState, generate_patch_tree, load_config, load_motd};
-use elseware::ship::ship::ShipServerStateBuilder;
+use elseware::ship::ship::{ShipServerStateBuilder, ShipEvent};
 
 #[allow(unused_imports)]
 use elseware::entity::gateway::{EntityGateway, InMemoryGateway, PostgresGateway};
@@ -364,6 +364,7 @@ fn main() {
             .name("US/Sona-Nyl".into())
             .ip(Ipv4Addr::new(127,0,0,1))
             .port(elseware::ship::ship::SHIP_PORT)
+            .event(ShipEvent::Halloween)
             .gateway(entity_gateway.clone())
             .build();
         let sub_ship_state = ship_state.clone();
@@ -378,7 +379,8 @@ fn main() {
         let ship_state = ShipServerStateBuilder::default()
             .name("EU/Dylath-Leen".into())
             .ip(Ipv4Addr::new(127,0,0,1))
-            .port(elseware::ship::ship::SHIP_PORT+200)
+            .port(elseware::ship::ship::SHIP_PORT+2000)
+            .event(ShipEvent::Christmas)
             .gateway(entity_gateway.clone())
             .build();
         let sub_ship_state = ship_state.clone();
diff --git a/src/common/mainloop/client.rs b/src/common/mainloop/client.rs
index 6cf7de4..758d075 100644
--- a/src/common/mainloop/client.rs
+++ b/src/common/mainloop/client.rs
@@ -205,6 +205,7 @@ where
     loop {
         match packet_queue.recv().await {
             Ok(pkt) => {
+                info!("[send to {:?}] {:#?}", client_id, pkt);
                 if let Err(err) = send_pkt(&mut socket, &mut cipher, &pkt).await {
                     warn!("error sending pkt {:#?} to {:?} {:?}", pkt, client_id, err);
                 }
diff --git a/src/lib.rs b/src/lib.rs
index 068f6e9..176a9fe 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,8 +7,6 @@
 #![feature(test)]
 
 extern crate test;
-extern crate fix_hidden_lifetime_bug;
-
 
 pub mod common;
 pub mod entity;
diff --git a/src/ship/location.rs b/src/ship/location.rs
index 037df94..f58a6f9 100644
--- a/src/ship/location.rs
+++ b/src/ship/location.rs
@@ -153,13 +153,9 @@ pub struct ClientLocation {
 
 impl Default for ClientLocation {
     fn default() -> ClientLocation {
-        //const RNONE: Option<Arc<RwLock<Room>>> = None;
-        //const LINIT: Arc<RwLock<Lobby>> = Arc::new(RwLock::new(Lobby([None; 12])));
         ClientLocation {
-            //lobbies: [LINIT; 15],
             lobbies: core::array::from_fn(|_| Arc::new(RwLock::new(Lobby([None; 12])))),
             rooms: core::array::from_fn(|_| None),
-            //rooms: [RNONE; MAX_ROOMS],
             client_location: Arc::new(RwLock::new(HashMap::new())),
         }
     }
diff --git a/src/ship/map/enemy.rs b/src/ship/map/enemy.rs
index b5efd7c..79dc3b8 100644
--- a/src/ship/map/enemy.rs
+++ b/src/ship/map/enemy.rs
@@ -5,6 +5,7 @@ use std::collections::HashMap;
 use byteorder::{LittleEndian, ReadBytesExt};
 use thiserror::Error;
 
+use crate::ship::ship::ShipEvent;
 use crate::ship::monster::MonsterType;
 use crate::ship::room::Episode;
 
@@ -87,10 +88,9 @@ impl RareMonsterAppearTable {
 
         let appear_rates: HashMap<MonsterType, f32> = cfg
             .into_iter()
-            .map(|(monster, rate)| {
-                let monster: MonsterType = monster.parse().unwrap(); // TODO: don't unwrap!
-                let appear_rate = rate;
-                (monster, appear_rate)
+            .filter_map(|(monster, rate)| {
+                let monster: MonsterType = monster.parse().ok()?;
+                Some((monster, rate))
             })
             .collect();
 
@@ -99,11 +99,8 @@ impl RareMonsterAppearTable {
         }
     }
 
-    pub fn roll_appearance(&self, monster: &MonsterType) -> bool {
-        if rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32) {
-            return true
-        }
-        false
+    pub fn roll_is_rare(&self, monster: &MonsterType) -> bool {
+        rand_chacha::ChaChaRng::from_entropy().gen::<f32>() < *self.appear_rate.get(monster).unwrap_or(&0.0f32)
     }
 }
 
@@ -324,7 +321,7 @@ impl MapEnemy {
         }
     }
 
-    pub fn has_rare_appearance(self) -> bool {
+    pub fn can_be_rare(&self) -> bool {
         matches!(self.monster,
             MonsterType::RagRappy | MonsterType::Hildebear |
             MonsterType::PoisonLily | MonsterType::PofuillySlime |
@@ -339,32 +336,26 @@ impl MapEnemy {
     guaranteed rare monsters don't count towards the limit
     */
     #[must_use]
-    pub fn set_rare_appearance(self) -> MapEnemy {
-        match (self.monster, self.map_area.to_episode()) {
-            (MonsterType::RagRappy, Episode::One) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
-            (MonsterType::RagRappy, Episode::Two) => {MapEnemy {monster: MonsterType::EventRappy, shiny:true, ..self}},
-            (MonsterType::Hildebear, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
-            (MonsterType::PoisonLily, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
-            (MonsterType::PofuillySlime, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
-            (MonsterType::SandRappyCrater, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
-            (MonsterType::ZuCrater, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
-            (MonsterType::Dorphon, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
-            (MonsterType::SandRappyDesert, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
-            (MonsterType::ZuDesert, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
-            (MonsterType::MerissaA, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
-            (MonsterType::SaintMillion, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
-            (MonsterType::Shambertin, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
+    pub fn into_rare(self, event: ShipEvent) -> MapEnemy {
+        match (self.monster, self.map_area.to_episode(), event) {
+            (MonsterType::RagRappy, Episode::One, _) => {MapEnemy {monster: MonsterType::AlRappy, shiny:true, ..self}},
+            (MonsterType::RagRappy, Episode::Two, ShipEvent::Easter) => {MapEnemy {monster: MonsterType::EasterRappy, shiny:true, ..self}},
+            (MonsterType::RagRappy, Episode::Two, ShipEvent::Halloween) => {MapEnemy {monster: MonsterType::HalloRappy, shiny:true, ..self}},
+            (MonsterType::RagRappy, Episode::Two, ShipEvent::Christmas) => {MapEnemy {monster: MonsterType::StRappy, shiny:true, ..self}},
+            (MonsterType::RagRappy, Episode::Two, _) => {MapEnemy {monster: MonsterType::LoveRappy, shiny:true, ..self}},
+            (MonsterType::Hildebear, _, _) => {MapEnemy {monster: MonsterType::Hildeblue, shiny:true, ..self}},
+            (MonsterType::PoisonLily, _, _) => {MapEnemy {monster: MonsterType::NarLily, shiny:true, ..self}},
+            (MonsterType::PofuillySlime, _, _) => {MapEnemy {monster: MonsterType::PouillySlime, shiny:true, ..self}},
+            (MonsterType::SandRappyCrater, _, _) => {MapEnemy {monster: MonsterType::DelRappyCrater, shiny:true, ..self}},
+            (MonsterType::ZuCrater, _, _) => {MapEnemy {monster: MonsterType::PazuzuCrater, shiny:true, ..self}},
+            (MonsterType::Dorphon, _, _) => {MapEnemy {monster: MonsterType::DorphonEclair, shiny:true, ..self}},
+            (MonsterType::SandRappyDesert, _, _) => {MapEnemy {monster: MonsterType::DelRappyDesert, shiny:true, ..self}},
+            (MonsterType::ZuDesert, _, _) => {MapEnemy {monster: MonsterType::PazuzuDesert, shiny:true, ..self}},
+            (MonsterType::MerissaA, _, _) => {MapEnemy {monster: MonsterType::MerissaAA, shiny:true, ..self}},
+            (MonsterType::SaintMillion, _, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
+            (MonsterType::Shambertin, _, _) => {MapEnemy {monster: MonsterType::Kondrieu, shiny:true, ..self}},
             _ => {self},
         }
     }
-    
-    // in theory this should only be called on monsters we know can have rare types
-    #[must_use]
-    pub fn roll_appearance_for_mission(self, rare_monster_table: &RareMonsterAppearTable) -> MapEnemy {
-        if rare_monster_table.roll_appearance(&self.monster) {
-            return self.set_rare_appearance()
-        }
-        self
-    }
 }
 
diff --git a/src/ship/map/maps.rs b/src/ship/map/maps.rs
index 6c971fc..5d04291 100644
--- a/src/ship/map/maps.rs
+++ b/src/ship/map/maps.rs
@@ -6,6 +6,7 @@ use std::fs::File;
 
 use thiserror::Error;
 
+use crate::ship::ship::ShipEvent;
 use crate::ship::monster::MonsterType;
 use crate::ship::room::{Episode, RoomMode};
 
@@ -186,7 +187,7 @@ pub struct Maps {
 }
 
 impl Maps {
-    pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable) -> Maps {
+    pub fn new(room_mode: RoomMode, rare_monster_table: &enemy::RareMonsterAppearTable, event: ShipEvent) -> Maps {
         let map_variants = match (room_mode.episode(), room_mode.single_player()) {
             (Episode::One, 0) => {
                 vec![MapVariant::new(MapArea::Pioneer2Ep1, MapVariantMode::Online),
@@ -278,20 +279,19 @@ impl Maps {
             _ => unreachable!()
         };
 
-        let mut maps = Maps {
+        Maps {
             enemy_data: map_variants.iter()
-                        .fold(Vec::new(), |mut enemy_data, map_variant| {
-                            enemy_data.append(&mut enemy_data_from_map_data(map_variant, &room_mode.episode()));
-                            enemy_data
-                        }),
+                .flat_map(|map_variant| {
+                    enemy_data_from_map_data(map_variant, &room_mode.episode())
+                })
+                .map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
+                .collect(),
             object_data: map_variants.iter()
-                        .flat_map(|map_variant| {
-                            objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
-                        }).collect(),
-                        map_variants,
-        };
-        maps.roll_monster_appearance(rare_monster_table);
-        maps
+                .flat_map(|map_variant| {
+                    objects_from_map_data(map_variant.obj_file().into(), &room_mode.episode(), &map_variant.map)
+                }).collect(),
+            map_variants,
+        }
     }
 
     pub fn enemy_by_id(&self, id: usize) -> Result<MapEnemy, MapsError> {
@@ -314,9 +314,16 @@ impl Maps {
             })
     }
 
-    pub fn set_quest_data(&mut self, enemies: Vec<Option<MapEnemy>>, objects: Vec<Option<MapObject>>, rare_monster_appear_table: &RareMonsterAppearTable) {
-        self.enemy_data = enemies;
-        self.roll_monster_appearance(rare_monster_appear_table);
+    pub fn set_quest_data(&mut self,
+                          enemies: Vec<Option<MapEnemy>>,
+                          objects: Vec<Option<MapObject>>,
+                          rare_monster_table: &RareMonsterAppearTable,
+                          event: ShipEvent)
+    {
+        self.enemy_data = enemies
+            .into_iter()
+            .map(|enemy| apply_rare_enemy(enemy, rare_monster_table, event))
+            .collect();
         self.object_data = objects;
     }
 
@@ -325,10 +332,11 @@ impl Maps {
         let shiny: Vec<(usize, &Option<MapEnemy>)> = self.enemy_data.iter()
             .enumerate()
             .filter(|(_,m)| {
-                if m.is_some() {
-                    m.unwrap().shiny
-                } else {
-                    false
+                match m {
+                    Some(m) => {
+                        m.shiny
+                    },
+                    None => false,
                 }
             })
             .collect();
@@ -341,21 +349,15 @@ impl Maps {
         }
         rare_monsters
     }
-
-    pub fn roll_monster_appearance(&mut self, rare_monster_table: &RareMonsterAppearTable) {
-        self.enemy_data = self.enemy_data
-                            .iter()
-                            .map(|&x|
-                                if let Some(monster) = x {
-                                    if monster.has_rare_appearance() {
-                                        Some(monster.roll_appearance_for_mission(rare_monster_table))
-                                    } else {
-                                        Some(monster)
-                                    }
-                                } else {
-                                    x
-                                }
-                            )
-                            .collect();
-    }
+}
+
+fn apply_rare_enemy(enemy: Option<MapEnemy>, rare_enemy_table: &RareMonsterAppearTable, event: ShipEvent) -> Option<MapEnemy> {
+    enemy.map(|enemy| {
+        if enemy.can_be_rare() && rare_enemy_table.roll_is_rare(&enemy.monster) {
+            enemy.into_rare(event)
+        }
+        else {
+            enemy
+        }
+    })
 }
diff --git a/src/ship/packet/builder/lobby.rs b/src/ship/packet/builder/lobby.rs
index 2571766..666d9be 100644
--- a/src/ship/packet/builder/lobby.rs
+++ b/src/ship/packet/builder/lobby.rs
@@ -1,6 +1,6 @@
 use libpso::packet::ship::*;
 use crate::common::serverstate::ClientId;
-use crate::ship::ship::{ShipError, Clients};
+use crate::ship::ship::{ShipError, Clients, ShipEvent};
 use crate::ship::location::{ClientLocation, LobbyId, ClientLocationError};
 use crate::ship::packet::builder::{player_info};
 use crate::ship::items::state::ItemState;
@@ -11,7 +11,8 @@ pub async fn join_lobby(id: ClientId,
                         lobby: LobbyId,
                         client_location: &ClientLocation,
                         clients: &Clients,
-                        item_state: &ItemState)
+                        item_state: &ItemState,
+                        event: ShipEvent)
                         -> Result<JoinLobby, ShipError> {
     let lobby_clients = client_location.get_clients_in_lobby(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
 
@@ -41,7 +42,7 @@ pub async fn join_lobby(id: ClientId,
         one: 1,
         lobby: lobby.id(),
         block: client_block,
-        event: 0,
+        event: event.into(),
         padding: 0,
         playerinfo,
     })
@@ -51,7 +52,8 @@ pub async fn add_to_lobby(id: ClientId,
                           lobby: LobbyId,
                           client_location: &ClientLocation,
                           clients: &Clients,
-                          item_state: &ItemState)
+                          item_state: &ItemState,
+                          event: ShipEvent)
                           -> Result<AddToLobby, ShipError> {
     let area_client = client_location.get_local_client(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
     let leader = client_location.get_lobby_leader(lobby).await.map_err(|err| -> ClientLocationError { err.into() })?;
@@ -66,7 +68,7 @@ pub async fn add_to_lobby(id: ClientId,
                 one: 1,
                 lobby: lobby.id(),
                 block: client.block as u16,
-                event: 0,
+                event: event.into(),
                 padding: 0,
                 playerinfo: player_info(0x100, client, &area_client, &inventory).await,
             })
diff --git a/src/ship/packet/builder/room.rs b/src/ship/packet/builder/room.rs
index 4ff4696..b50c7fb 100644
--- a/src/ship/packet/builder/room.rs
+++ b/src/ship/packet/builder/room.rs
@@ -1,6 +1,6 @@
 use libpso::packet::ship::*;
 use crate::common::serverstate::ClientId;
-use crate::ship::ship::{ShipError, ClientState, Clients};
+use crate::ship::ship::{ShipError, ClientState, Clients, ShipEvent};
 use crate::ship::location::{ClientLocation, RoomId, AreaClient, ClientLocationError};
 use crate::ship::room::RoomState;
 use crate::ship::items::state::ItemState;
@@ -13,7 +13,8 @@ pub async fn join_room(id: ClientId,
                        clients: &Clients,
                        client_location: &ClientLocation,
                        room_id: RoomId,
-                       room: &RoomState)
+                       room: &RoomState,
+                       event: ShipEvent)
                        -> Result<JoinRoom, ShipError> {
     let all_clients = client_location.get_clients_in_room(room_id).await.map_err(|err| -> ClientLocationError { err.into() })?;
     let players = futures::stream::iter(all_clients.iter())
@@ -40,7 +41,7 @@ pub async fn join_room(id: ClientId,
         one: 1,
         difficulty: room.mode.difficulty().into(),
         battle: room.mode.battle(),
-        event: 0,
+        event: event.into(),
         section: room.section_id.into(),
         challenge: room.mode.challenge(),
         random_seed: room.random_seed,
@@ -53,13 +54,12 @@ pub async fn join_room(id: ClientId,
 
 
 pub async fn add_to_room(_id: ClientId,
-                   client: &ClientState,
-                   area_client: &AreaClient,
-                   leader: &AreaClient,
-                   item_state: &ItemState,
-                   _room_id: RoomId,
-)
-                   -> Result<AddToRoom, ShipError> {
+                         client: &ClientState,
+                         area_client: &AreaClient,
+                         leader: &AreaClient,
+                         item_state: &ItemState,
+                         event: ShipEvent)
+                         -> Result<AddToRoom, ShipError> {
     let inventory = item_state.get_character_inventory(&client.character).await?;
     Ok(AddToRoom {
         flag: 1,
@@ -68,7 +68,7 @@ pub async fn add_to_room(_id: ClientId,
         one: 0, // TODO: ????????
         lobby: 0xFF,
         block: 0,
-        event: 0,
+        event: event.into(),
         padding: 0,
         playerinfo: player_info(0x10000, client, area_client, &inventory).await,
     })
diff --git a/src/ship/packet/handler/lobby.rs b/src/ship/packet/handler/lobby.rs
index 287ea41..f0caf23 100644
--- a/src/ship/packet/handler/lobby.rs
+++ b/src/ship/packet/handler/lobby.rs
@@ -1,7 +1,7 @@
 use libpso::packet::ship::*;
 use crate::common::serverstate::ClientId;
 use crate::common::leveltable::LEVEL_TABLE;
-use crate::ship::ship::{SendShipPacket, ShipError, Clients};
+use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
 use crate::ship::room::Rooms;
 use crate::ship::character::{FullCharacterBytesBuilder};
 use crate::ship::location::{ClientLocation, LobbyId, RoomLobby, ClientLocationError, RoomId};
@@ -55,11 +55,12 @@ pub async fn send_player_to_lobby(id: ClientId,
                                   _pkt: CharData,
                                   client_location: &mut ClientLocation,
                                   clients: &Clients,
-                                  item_state: &ItemState)
+                                  item_state: &ItemState,
+                                  event: ShipEvent)
                                   -> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
     let lobby = client_location.add_client_to_next_available_lobby(id, LobbyId(0)).await.map_err(|_| ShipError::TooManyClients)?;
-    let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state).await?;
-    let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state).await?;
+    let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, event).await?;
+    let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, event).await?;
     let neighbors = client_location.get_client_neighbors(id).await.unwrap();
     Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))]
        .into_iter()
@@ -74,7 +75,8 @@ pub async fn change_lobby<EG>(id: ClientId,
                               clients: &Clients,
                               item_state: &mut ItemState,
                               rooms: &Rooms,
-                              entity_gateway: &mut EG)
+                              entity_gateway: &mut EG,
+                              event: ShipEvent)
                               -> Result<Vec<(ClientId, SendShipPacket)>, ShipError>
 where
     EG: EntityGateway + Clone + 'static,
@@ -117,16 +119,17 @@ where
         Box::pin(async move {
             item_state.load_character(&mut entity_gateway, &client.character).await
         })}).await??;
-    let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state).await?;
-    let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state).await?;
+    let join_lobby = packet::builder::lobby::join_lobby(id, lobby, client_location, clients, item_state, event).await?;
+    let addto = packet::builder::lobby::add_to_lobby(id, lobby, client_location, clients, item_state, event).await?;
     let neighbors = client_location.get_client_neighbors(id).await?;
     Ok(vec![(id, SendShipPacket::JoinLobby(join_lobby))]
-        .into_iter()
-        .chain(neighbors.into_iter()
-            .map(|c| (c.client, SendShipPacket::AddToLobby(addto.clone()))))
-        .chain(old_neighbors.into_iter()
-            .map(|c| (c.client, SendShipPacket::LeaveLobby(leave_lobby.clone()))))
-        .collect())
+       .into_iter()
+       .chain(neighbors.into_iter()
+              .map(|c| (c.client, SendShipPacket::AddToLobby(addto.clone()))))
+       .chain(old_neighbors.into_iter()
+              .map(|c| (c.client, SendShipPacket::LeaveLobby(leave_lobby.clone()))))
+       .chain(std::iter::once((id, SendShipPacket::LobbyEvent(LobbyEvent{ event: event.into()}))))
+       .collect())
 }
 
 pub async fn remove_from_lobby(id: ClientId,
diff --git a/src/ship/packet/handler/quest.rs b/src/ship/packet/handler/quest.rs
index 9df89b9..a9a5cb2 100644
--- a/src/ship/packet/handler/quest.rs
+++ b/src/ship/packet/handler/quest.rs
@@ -2,7 +2,7 @@ use std::io::{Cursor, Read, Seek, SeekFrom};
 use futures::stream::{FuturesOrdered, StreamExt};
 use libpso::packet::ship::*;
 use crate::common::serverstate::ClientId;
-use crate::ship::ship::{SendShipPacket, ShipError, Clients};
+use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
 use crate::ship::room::Rooms;
 use crate::ship::location::{ClientLocation, ClientLocationError};
 use crate::ship::packet::builder::quest;
@@ -94,7 +94,8 @@ pub async fn player_chose_quest(id: ClientId,
                                 questmenuselect: QuestMenuSelect,
                                 clients: &Clients,
                                 client_location: &ClientLocation,
-                                rooms: &Rooms)
+                                rooms: &Rooms,
+                                event: ShipEvent)
                                 -> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
     let room_id = client_location.get_room(id).await.map_err(|err| -> ClientLocationError { err.into() })?;
 
@@ -115,7 +116,7 @@ pub async fn player_chose_quest(id: ClientId,
                 .clone();
 
             let rare_monster_drops = room.rare_monster_table.clone();
-            room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_drops);
+            room.maps.set_quest_data(quest.enemies.clone(), quest.objects.clone(), &rare_monster_drops, event);
             room.map_areas = quest.map_areas.clone();
 
             let bin = quest::quest_header(&questmenuselect, &quest.bin_blob, "bin");
diff --git a/src/ship/packet/handler/room.rs b/src/ship/packet/handler/room.rs
index 7653abc..435d4b8 100644
--- a/src/ship/packet/handler/room.rs
+++ b/src/ship/packet/handler/room.rs
@@ -5,7 +5,7 @@ use libpso::packet::ship::*;
 use libpso::packet::messages::*;
 use crate::common::serverstate::ClientId;
 use crate::common::leveltable::LEVEL_TABLE;
-use crate::ship::ship::{SendShipPacket, ShipError, Clients};
+use crate::ship::ship::{SendShipPacket, ShipError, Clients, ShipEvent};
 use crate::ship::room::Rooms;
 use crate::ship::location::{ClientLocation, RoomId, RoomLobby, GetAreaError};
 use crate::ship::packet::builder;
@@ -17,7 +17,8 @@ pub async fn create_room(id: ClientId,
                          client_location: &mut ClientLocation,
                          clients: &Clients,
                          item_state: &mut ItemState,
-                         rooms: &Rooms)
+                         rooms: &Rooms,
+                         event: ShipEvent)
                          -> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
     let level = clients.with(id, |client| Box::pin(async move {
         LEVEL_TABLE.get_level_from_exp(client.character.char_class, client.character.exp)
@@ -44,12 +45,12 @@ pub async fn create_room(id: ClientId,
         let mut item_state = item_state.clone();
         Box::pin(async move {
             item_state.add_character_to_room(room_id, &client.character, area_client).await;
-            let mut room = room::RoomState::from_create_room(&create_room, client.character.section_id)?;
+            let mut room = room::RoomState::from_create_room(&create_room, client.character.section_id, event)?;
             room.bursting = true;
             Ok::<_, ShipError>(room)
         })}).await??;
 
-    let join_room = builder::room::join_room(id, clients, client_location, room_id, &room).await?;
+    let join_room = builder::room::join_room(id, clients, client_location, room_id, &room, event).await?;
     rooms.add(room_id, room).await?;
 
     let mut result = vec![(id, SendShipPacket::JoinRoom(join_room))];
@@ -87,7 +88,8 @@ pub async fn join_room(id: ClientId,
                        client_location: &mut ClientLocation,
                        clients: &Clients,
                        item_state: &mut ItemState,
-                       rooms: &Rooms)
+                       rooms: &Rooms,
+                       event: ShipEvent)
                        -> Result<Vec<(ClientId, SendShipPacket)>, ShipError> {
     let room_id = RoomId(pkt.item as usize);
     if !rooms.exists(room_id).await {
@@ -134,12 +136,12 @@ pub async fn join_room(id: ClientId,
         let clients = clients.clone();
         let client_location = client_location.clone();
         Box::pin(async move {
-            builder::room::join_room(id, &clients, &client_location, room_id, room).await
+            builder::room::join_room(id, &clients, &client_location, room_id, room, event).await
         })}).await??;
     let add_to = clients.with(id, |client| {
         let item_state = item_state.clone();
         Box::pin(async move {
-            builder::room::add_to_room(id, client, &area_client, &room_leader, &item_state, room_id).await
+            builder::room::add_to_room(id, client, &area_client, &room_leader, &item_state, event).await
         })}).await??;
 
     rooms.with_mut(room_id, |room| Box::pin(async move {
diff --git a/src/ship/room.rs b/src/ship/room.rs
index 9758064..ed41b8d 100644
--- a/src/ship/room.rs
+++ b/src/ship/room.rs
@@ -15,7 +15,7 @@ use crate::ship::monster::{load_monster_stats_table, MonsterType, MonsterStats};
 use crate::ship::map::area::MapAreaLookup;
 use crate::ship::map::enemy::RareMonsterAppearTable;
 use crate::ship::quests;
-use crate::ship::ship::ShipError;
+use crate::ship::ship::{ShipError, ShipEvent};
 use crate::ship::location::{MAX_ROOMS, RoomId};
 
 
@@ -343,7 +343,7 @@ impl RoomState {
         self.quest_group = QuestCategoryType::from(group);
     }
 
-    pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID) -> Result<RoomState, RoomCreationError> {
+    pub fn from_create_room(create_room: &libpso::packet::ship::CreateRoom, section_id: SectionID, event: ShipEvent) -> Result<RoomState, RoomCreationError> {
         if [create_room.battle, create_room.challenge, create_room.single_player].iter().sum::<u8>() > 1 {
             return Err(RoomCreationError::InvalidMode)
         }
@@ -410,7 +410,7 @@ impl RoomState {
             rare_monster_table: Box::new(rare_monster_table.clone()),
             name: String::from_utf16_lossy(&create_room.name).trim_matches(char::from(0)).into(),
             password: create_room.password,
-            maps: Maps::new(room_mode, &rare_monster_table), // TODO: rare_monster_table here feels janky. is there some way to call the the RoomState.rare_monster_table we already created?
+            maps: Maps::new(room_mode, &rare_monster_table, event),
             section_id,
             drop_table: Box::new(DropTable::new(room_mode.episode(), room_mode.difficulty(), section_id)),
             bursting: false,
diff --git a/src/ship/ship.rs b/src/ship/ship.rs
index 4dcd8c7..d183ebf 100644
--- a/src/ship/ship.rs
+++ b/src/ship/ship.rs
@@ -42,6 +42,57 @@ pub const SHIP_PORT: u16 = 23423;
 pub const QUEST_CATEGORY_MENU_ID: u32 = 0xA2;
 pub const QUEST_SELECT_MENU_ID: u32 = 0xA3;
 
+#[derive(Clone, Copy)]
+pub enum ShipEvent {
+    None,
+    Christmas,
+    Valentines,
+    Easter,
+    Halloween,
+    Sonic,
+    NewYear,
+    Summer,
+    White,
+    Wedding,
+    Fall,
+    Spring,
+    Summer2,
+    Spring2,
+}
+
+impl From<ShipEvent> for u32 {
+    fn from(other: ShipEvent) -> u32 {
+        u16::from(other) as u32
+    }
+}
+
+impl From<ShipEvent> for u16 {
+    fn from(other: ShipEvent) -> u16 {
+        u8::from(other) as u16
+    }
+}
+
+impl From<ShipEvent> for u8 {
+    fn from(other: ShipEvent) -> u8 {
+        match other {
+            ShipEvent::None => 0,
+            ShipEvent::Christmas => 1,
+            ShipEvent::Valentines => 3,
+            ShipEvent::Easter => 4,
+            ShipEvent::Halloween => 5,
+            ShipEvent::Sonic => 6,
+            ShipEvent::NewYear => 7,
+            ShipEvent::Summer => 8,
+            ShipEvent::White => 9,
+            ShipEvent::Wedding => 10,
+            ShipEvent::Fall => 11,
+            ShipEvent::Spring => 12,
+            ShipEvent::Summer2 => 13,
+            ShipEvent::Spring2 => 14,
+        }
+    }
+}
+
 
 #[derive(Error, Debug)]
 pub enum ShipError {
@@ -240,6 +291,7 @@ pub enum SendShipPacket {
     AcknowledgeTrade(AcknowledgeTrade),
     CancelTrade(CancelTrade),
     TradeSuccessful(TradeSuccessful),
+    LobbyEvent(LobbyEvent),
 }
 
 impl SendServerPacket for SendShipPacket {
@@ -282,6 +334,7 @@ impl SendServerPacket for SendShipPacket {
             SendShipPacket::AcknowledgeTrade(pkt) => pkt.as_bytes(),
             SendShipPacket::CancelTrade(pkt) => pkt.as_bytes(),
             SendShipPacket::TradeSuccessful(pkt) => pkt.as_bytes(),
+            SendShipPacket::LobbyEvent(pkt) => pkt.as_bytes(),
         }
     }
 }
@@ -321,6 +374,7 @@ pub struct ShipServerStateBuilder<EG: EntityGateway + Clone + 'static> {
     ip: Option<Ipv4Addr>,
     port: Option<u16>,
     auth_token: Option<AuthToken>,
+    event: Option<ShipEvent>,
     num_blocks: usize,
 }
 
@@ -332,6 +386,7 @@ impl<EG: EntityGateway + Clone + 'static> Default for ShipServerStateBuilder<EG>
             ip: None,
             port: None,
             auth_token: None,
+            event: None,
             num_blocks: 2,
         }
     }
@@ -368,6 +423,12 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
         self
     }
 
+    #[must_use]
+    pub fn event(mut self, event: ShipEvent) -> ShipServerStateBuilder<EG> {
+        self.event = Some(event);
+        self
+    }
+
     #[must_use]
     pub fn blocks(mut self, num_blocks: usize) -> ShipServerStateBuilder<EG> {
         self.num_blocks = num_blocks;
@@ -385,6 +446,7 @@ impl<EG: EntityGateway + Clone + 'static> ShipServerStateBuilder<EG> {
             port: self.port.unwrap_or(SHIP_PORT),
             shops: ItemShops::default(),
             blocks: Blocks(blocks),
+            event: self.event.unwrap_or(ShipEvent::None),
 
             auth_token: self.auth_token.unwrap_or_else(|| AuthToken("".into())),
             ship_list: Vec::new(),
@@ -424,6 +486,7 @@ pub struct ShipServerState<EG: EntityGateway + Clone + 'static> {
     item_state: items::state::ItemState,
     shops: ItemShops,
     pub blocks: Blocks,
+    event: ShipEvent,
 
     ip: Ipv4Addr,
     port: u16,
@@ -611,14 +674,14 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
                         let select_block = handler::lobby::block_selected(id, menuselect, &self.clients, &self.item_state).await?.into_iter();
                         leave_lobby.chain(select_block).collect()
                     }
-                    ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms).await?,
+                    ROOM_MENU_ID => handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?,
                     QUEST_CATEGORY_MENU_ID => handler::quest::select_quest_category(id, menuselect, &block.client_location, &block.rooms).await?,
                     _ => unreachable!(),
                 }
             },
             RecvShipPacket::QuestMenuSelect(questmenuselect) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
-                handler::quest::player_chose_quest(id, questmenuselect, &self.clients, &block.client_location, &block.rooms).await?
+                handler::quest::player_chose_quest(id, questmenuselect, &self.clients, &block.client_location, &block.rooms, self.event).await?
             },
             RecvShipPacket::MenuDetail(menudetail) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
@@ -636,7 +699,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
                         menu: room_password_req.menu,
                         item: room_password_req.item,
                     };
-                    handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms).await?
+                    handler::room::join_room(id, menuselect, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?
                 }
                 else {
                     vec![(id, SendShipPacket::SmallDialog(SmallDialog::new("Incorrect password".into())))]
@@ -644,7 +707,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
             },
             RecvShipPacket::CharData(chardata) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
-                handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state).await?
+                handler::lobby::send_player_to_lobby(id, chardata, &mut block.client_location, &self.clients, &self.item_state, self.event).await?
             },
             RecvShipPacket::Message(msg) => {
                 self.message(id, msg).await?
@@ -658,7 +721,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
             },
             RecvShipPacket::CreateRoom(create_room) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
-                handler::room::create_room(id, create_room, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms).await?
+                handler::room::create_room(id, create_room, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, self.event).await?
             },
             RecvShipPacket::RoomNameRequest(_req) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
@@ -696,7 +759,7 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
             },
             RecvShipPacket::LobbySelect(pkt) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
-                handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, &mut self.entity_gateway).await?
+                handler::lobby::change_lobby(id, pkt.lobby, &mut block.client_location, &self.clients, &mut self.item_state, &block.rooms, &mut self.entity_gateway, self.event).await?
             },
             RecvShipPacket::RequestQuestList(rql) => {
                 let block = self.blocks.get_from_client(id, &self.clients).await?;
@@ -744,7 +807,6 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
     }
 
     async fn on_disconnect(&mut self, id: ClientId) -> Result<Vec<(ClientId, SendShipPacket)>, anyhow::Error> {
-        //let client = self.clients.get(&id).ok_or(ShipError::ClientNotFound(id))?;
         let block = self.blocks.get_from_client(id, &self.clients).await?;
         let area_client = block.client_location.get_local_client(id).await?;
         let neighbors = block.client_location.get_client_neighbors(id).await?;
@@ -763,17 +825,6 @@ impl<EG: EntityGateway + Clone> ServerState for ShipServerState<EG> {
             }
         };
 
-        /*
-        if let Some(shipgate_sender) = self.shipgate_sender.as_ref() {
-            shipgate_sender.send(ShipMessage::RemoveUser(client.user.id)).await;
-        }
-
-        block.client_location.remove_client_from_area(id).await;
-        self.clients.with(id, |client| Box::pin(async move {
-            self.item_state.remove_character_from_room(&client.character).await
-        })).await?;
-         */
-
         if let Some(mut client) = self.clients.remove(&id).await {
             client.user.at_ship = false;
             self.entity_gateway.save_user(&client.user).await;