use flatland_client_lib::{
AutoNavigator, ClientKeyBindings, GameClient, PlayConnection, RotationEditorMode,
};
use flatland_client_ui::{
editor_ability_choices, next_custom_preset, preset_deletable, InputAction, UiKeyCode,
UiKeyEvent, UiKeyEventKind,
};
pub struct ActionCtx<'a> {
pub block_active: &'a mut bool,
pub auto_nav: &'a mut Option<AutoNavigator>,
}
pub async fn dispatch_menu_key<S: PlayConnection>(
client: &mut GameClient<S>,
key: UiKeyEvent,
) -> bool {
if key.kind != UiKeyEventKind::Press {
return false;
}
if client.state.social_chat.input_focused {
match key.code {
UiKeyCode::Esc => client.state.social_chat.unfocus(),
UiKeyCode::Enter => {
if let Err(err) = client.submit_social_chat_buffer().await {
client.state.push_log(format!("Chat failed: {err}"));
}
}
UiKeyCode::Tab => client.state.social_chat.toggle_mode_speak_whisper(),
UiKeyCode::Backspace => {
client.state.social_chat.buffer.pop();
}
UiKeyCode::Char(c) => {
if !c.is_control()
&& client.state.social_chat.buffer.len()
< flatland_client_lib::SocialChatState::MAX_BUF
{
client.state.social_chat.buffer.push(c);
}
}
_ => {}
}
return true;
}
if client.state.show_worker_rename {
match key.code {
UiKeyCode::Esc => client.cancel_worker_rename(),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_worker_rename().await {
client.state.push_log(format!("Rename: {err}"));
}
}
UiKeyCode::Backspace => {
client.state.rename_buffer.pop();
}
UiKeyCode::Char(c) => {
if client.state.rename_buffer.chars().count() < 32 {
client.state.rename_buffer.push(c);
}
}
_ => {}
}
return true;
}
if client.state.show_equip_menu {
match key.code {
UiKeyCode::Char('p') | UiKeyCode::Esc => {
client.toggle_equip_menu();
}
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.equip_menu_index > 0 {
client.state.equip_menu_index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
let n = flatland_client_lib::equip_paperdoll_rows(&client.state).len();
if n > 0 {
client.state.equip_menu_index = (client.state.equip_menu_index + 1).min(n - 1);
}
}
UiKeyCode::PageUp => {
let n = flatland_client_lib::equip_paperdoll_rows(&client.state).len();
client.state.equip_menu_index =
flatland_client_lib::page_list_index(client.state.equip_menu_index, -1, n);
}
UiKeyCode::PageDown => {
let n = flatland_client_lib::equip_paperdoll_rows(&client.state).len();
client.state.equip_menu_index =
flatland_client_lib::page_list_index(client.state.equip_menu_index, 1, n);
}
UiKeyCode::Enter => {
if let Err(err) = client.activate_equip_selection().await {
client.state.push_log(format!("Equip: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_inventory_menu {
if client.state.show_rename_prompt {
match key.code {
UiKeyCode::Esc => client.cancel_rename_prompt(),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_rename_prompt().await {
client.state.push_log(format!("Rename: {err}"));
}
}
UiKeyCode::Backspace => {
client.state.rename_buffer.pop();
}
UiKeyCode::Char(c) => {
if client.state.rename_buffer.len() < 32 {
client.state.rename_buffer.push(c);
}
}
_ => {}
}
} else if client.state.show_destroy_picker {
match key.code {
UiKeyCode::Esc => {
if client.state.destroy_confirm_pending {
client.cancel_destroy_confirm();
} else {
client.close_destroy_picker();
}
}
UiKeyCode::Char('[') | UiKeyCode::Char('-') => {
if !client.state.destroy_confirm_pending {
client.destroy_picker_adjust_quantity(-1);
}
}
UiKeyCode::Char(']') | UiKeyCode::Char('=') => {
if !client.state.destroy_confirm_pending {
client.destroy_picker_adjust_quantity(1);
}
}
UiKeyCode::Char('a') => {
if !client.state.destroy_confirm_pending {
client.destroy_picker_set_quantity_max();
}
}
UiKeyCode::Enter => {
if let Err(err) = client.activate_inventory_selection().await {
client.state.push_log(format!("Destroy failed: {err}"));
}
}
_ => {}
}
} else if client.state.show_grant_picker {
let filter_focused = client
.state
.grant_picker
.as_ref()
.map(|p| p.filter_focused)
.unwrap_or(false);
match key.code {
UiKeyCode::Esc => {
if !client.clear_or_blur_inventory_filter() {
client.close_grant_picker();
}
}
UiKeyCode::PageUp => client.inventory_menu_page(-1),
UiKeyCode::PageDown => client.inventory_menu_page(1),
UiKeyCode::Char('/') if !filter_focused => client.focus_inventory_filter(),
UiKeyCode::Backspace if filter_focused => client.inventory_filter_backspace(),
UiKeyCode::Char(c) if filter_focused => client.append_inventory_filter_char(c),
UiKeyCode::Up | UiKeyCode::Char('k') if !filter_focused => {
client.inventory_menu_move(-1)
}
UiKeyCode::Down | UiKeyCode::Char('j') if !filter_focused => {
client.inventory_menu_move(1)
}
UiKeyCode::Enter if !filter_focused => {
if let Err(err) = client.activate_inventory_selection().await {
client.state.push_log(format!("Grant failed: {err}"));
}
}
_ => {}
}
} else if client.state.show_move_picker {
let filter_focused = client
.state
.move_picker
.as_ref()
.map(|p| p.filter_focused)
.unwrap_or(false);
match key.code {
UiKeyCode::Esc => {
if !client.clear_or_blur_inventory_filter() {
client.close_move_picker();
}
}
UiKeyCode::PageUp => client.inventory_menu_page(-1),
UiKeyCode::PageDown => client.inventory_menu_page(1),
UiKeyCode::Char('/') if !filter_focused => client.focus_inventory_filter(),
UiKeyCode::Backspace if filter_focused => client.inventory_filter_backspace(),
UiKeyCode::Char(c) if filter_focused => client.append_inventory_filter_char(c),
UiKeyCode::Up | UiKeyCode::Char('k') if !filter_focused => {
client.inventory_menu_move(-1)
}
UiKeyCode::Down | UiKeyCode::Char('j') if !filter_focused => {
client.inventory_menu_move(1)
}
UiKeyCode::Char('[') | UiKeyCode::Char('-') if !filter_focused => {
client.move_picker_adjust_quantity(-1);
}
UiKeyCode::Char(']') | UiKeyCode::Char('=') if !filter_focused => {
client.move_picker_adjust_quantity(1);
}
UiKeyCode::Char('a') if !filter_focused => client.move_picker_set_quantity_max(),
UiKeyCode::Enter if !filter_focused => {
if let Err(err) = client.activate_inventory_selection().await {
client.state.push_log(format!("Move failed: {err}"));
}
}
_ => {}
}
} else if client.state.inventory_filter_focused {
match key.code {
UiKeyCode::Esc => {
let _ = client.clear_or_blur_inventory_filter();
}
UiKeyCode::Enter => {
client.state.inventory_filter_focused = false;
}
UiKeyCode::Backspace => client.inventory_filter_backspace(),
UiKeyCode::Char(c) => client.append_inventory_filter_char(c),
_ => {}
}
} else {
match key.code {
UiKeyCode::Esc => {
if !client.clear_or_blur_inventory_filter() {
client.close_inventory_menu();
}
}
UiKeyCode::Char('b') => client.close_inventory_menu(),
UiKeyCode::Tab => client.cycle_inventory_tab(true),
UiKeyCode::BackTab => client.cycle_inventory_tab(false),
UiKeyCode::PageUp => client.inventory_menu_page(-1),
UiKeyCode::PageDown => client.inventory_menu_page(1),
UiKeyCode::Char('/') => client.focus_inventory_filter(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.inventory_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.inventory_menu_move(1),
UiKeyCode::Enter => {
if let Err(err) = client.activate_inventory_selection().await {
client.state.push_log(format!("{err}"));
}
}
UiKeyCode::Char('m') => {
if let Err(err) = client.open_move_picker() {
client.state.push_log(format!("{err}"));
}
}
UiKeyCode::Char('e') => {
if let Err(err) = client.use_selected_consumable().await {
client.state.push_log(format!("Use: {err}"));
}
}
UiKeyCode::Char('n') => {
if let Err(err) = client.open_rename_prompt() {
client.state.push_log(format!("{err}"));
}
}
UiKeyCode::Char('d') => {
if let Err(err) = client.drop_selected().await {
client.state.push_log(format!("Drop: {err}"));
}
}
UiKeyCode::Char('x') => {
if let Err(err) = client.open_destroy_picker() {
client.state.push_log(format!("Destroy: {err}"));
}
}
UiKeyCode::Char('l') => {
if let Err(err) = client.toggle_chest_lock_for_selection().await {
client.state.push_log(format!("Lock: {err}"));
}
}
UiKeyCode::Char('g') => {
if let Err(err) = client.give_selected_inventory_to_worker().await {
client.state.push_log(format!("Give: {err}"));
}
}
UiKeyCode::Char('u') => {
if let Err(err) = client.unequip_mainhand().await {
client.state.push_log(format!("Unequip: {err}"));
}
}
_ => {}
}
}
return true;
}
if client.state.show_keychain_menu {
match key.code {
UiKeyCode::Esc | UiKeyCode::Char(',') => client.close_keychain_menu(),
UiKeyCode::Up | UiKeyCode::Char('w') | UiKeyCode::Char('k') => {
client.keychain_menu_move(-1);
}
UiKeyCode::Down | UiKeyCode::Char('s') | UiKeyCode::Char('j') => {
client.keychain_menu_move(1);
}
UiKeyCode::PageUp => client.keychain_menu_page(-1),
UiKeyCode::PageDown => client.keychain_menu_page(1),
UiKeyCode::Enter => {
if let Err(err) = client.activate_keychain_selection().await {
client.state.push_log(format!("Keychain: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_craft_menu {
match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') | UiKeyCode::Char('n') | UiKeyCode::Char('c') => {
client.close_craft_menu()
}
UiKeyCode::Up | UiKeyCode::Char('k') => client.craft_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.craft_menu_move(1),
UiKeyCode::PageUp => client.craft_menu_page(-1),
UiKeyCode::PageDown => client.craft_menu_page(1),
UiKeyCode::Char('-') => client.craft_batch_adjust_quantity(-1),
UiKeyCode::Char('=') => client.craft_batch_adjust_quantity(1),
UiKeyCode::Char('a') => client.craft_batch_set_max(),
UiKeyCode::Enter => {
if let Err(err) = client.craft_menu_selection().await {
client.state.push_log(format!("Craft failed: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_quest_offer {
match key.code {
UiKeyCode::Esc => client.quest_offer_decline(),
UiKeyCode::Enter => {
if let Err(err) = client.quest_offer_accept().await {
client.state.push_log(format!("Quest: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_npc_chat {
match key.code {
UiKeyCode::Esc => {
if let Err(err) = client.npc_talk_close().await {
client.state.push_log(format!("Talk close failed: {err}"));
}
}
UiKeyCode::Enter => {
if let Err(err) = client.npc_talk_send().await {
client.state.push_log(format!("Talk failed: {err}"));
}
}
UiKeyCode::Backspace => {
if let Some(chat) = client.state.npc_chat.as_mut() {
chat.input.pop();
}
}
UiKeyCode::Char(c) => {
if let Some(chat) = client.state.npc_chat.as_mut() {
if !chat.pending && chat.input.len() < 300 {
chat.input.push(c);
}
}
}
_ => {}
}
return true;
}
if client.state.show_npc_verb_menu {
match key.code {
UiKeyCode::Esc => {
client.state.show_npc_verb_menu = false;
client.state.npc_verb_target = None;
}
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.npc_verb_index > 0 {
client.state.npc_verb_index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
let max = client.npc_verb_options().len().saturating_sub(1);
if client.state.npc_verb_index < max {
client.state.npc_verb_index += 1;
}
}
UiKeyCode::Enter => {
if let Err(err) = client.confirm_npc_verb().await {
client.state.push_log(format!("Interact failed: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.social_chat.pending_trade.is_some()
&& !client.state.social_chat.input_focused
&& !client.state.social_chat.picking_stone
&& matches!(key.code, UiKeyCode::Char('y' | 'Y' | 'n' | 'N'))
{
let accept = matches!(key.code, UiKeyCode::Char('y' | 'Y'));
if let Err(err) = client.respond_pending_trade(accept).await {
client.state.push_log(format!("Trade respond failed: {err}"));
}
return true;
}
if client.state.player_verbs.open {
let opts = flatland_client_lib::PlayerVerbState::options();
match key.code {
UiKeyCode::Esc => client.state.player_verbs.close(),
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.player_verbs.index > 0 {
client.state.player_verbs.index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
let max = opts.len().saturating_sub(1);
if client.state.player_verbs.index < max {
client.state.player_verbs.index += 1;
}
}
UiKeyCode::Enter => {
if let Err(err) = client.confirm_player_verb().await {
client.state.push_log(format!("Interact failed: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.social_chat.picking_stone {
let contacts =
flatland_client_lib::contacts_from_stacks(&client.state.whisper_pouch_stacks);
match key.code {
UiKeyCode::Esc => {
client.state.social_chat.picking_stone = false;
}
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.social_chat.stone_pick_index > 0 {
client.state.social_chat.stone_pick_index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
let max = contacts.len().saturating_sub(1);
if client.state.social_chat.stone_pick_index < max {
client.state.social_chat.stone_pick_index += 1;
}
}
UiKeyCode::Enter => {
if let Some(c) = contacts.get(client.state.social_chat.stone_pick_index) {
if !c.blank {
if let Some(peer) = client
.state
.entities
.iter()
.find(|e| e.id != client.state.entity_id && e.label == c.peer_label)
{
client
.state
.social_chat
.focus_stone(peer.id, &c.peer_label);
} else {
client.state.social_chat.push_system(format!(
"{} is not online in this region",
c.peer_label
));
client.state.social_chat.picking_stone = false;
}
}
}
}
_ => {}
}
return true;
}
if client.state.trade_ui.panel.is_some() {
if client.state.trade_ui.qty_entry.is_some() {
match key.code {
UiKeyCode::Esc => {
client.state.trade_ui.qty_entry = None;
client.state.trade_ui.picking_inventory = true;
}
UiKeyCode::Enter => {
if let Err(err) = client.trade_confirm_qty_or_present().await {
client.state.push_log(format!("Present failed: {err}"));
}
}
UiKeyCode::Char('[') | UiKeyCode::Char('-') => {
client.state.trade_ui.adjust_qty(-1);
}
UiKeyCode::Char(']') | UiKeyCode::Char('=') => {
client.state.trade_ui.adjust_qty(1);
}
UiKeyCode::Char('a') | UiKeyCode::Char('A') => {
client.state.trade_ui.set_qty_all();
}
UiKeyCode::Backspace => client.state.trade_ui.qty_backspace(),
UiKeyCode::Char(c) if c.is_ascii_digit() => {
client.state.trade_ui.append_qty_digit(c);
}
_ => {}
}
return true;
}
match key.code {
UiKeyCode::Esc => {
if client.state.trade_ui.picking_inventory {
client.state.trade_ui.picking_inventory = false;
} else if let Err(err) = client.trade_cancel().await {
client.state.push_log(format!("Trade cancel failed: {err}"));
}
}
UiKeyCode::Char('r') => {
let ready = client
.state
.trade_ui
.panel
.as_ref()
.map(|p| !p.i_ready)
.unwrap_or(true);
if let Err(err) = client.trade_set_ready(ready).await {
client.state.push_log(format!("Trade ready failed: {err}"));
}
}
UiKeyCode::Char('p') => {
client.state.trade_ui.picking_inventory = !client.state.trade_ui.picking_inventory;
client.state.trade_ui.qty_entry = None;
}
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.trade_ui.picking_inventory {
if client.state.trade_ui.inventory_index > 0 {
client.state.trade_ui.inventory_index -= 1;
}
} else if client.state.trade_ui.select_index > 0 {
client.state.trade_ui.select_index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
if client.state.trade_ui.picking_inventory {
let max = client.state.inventory_stacks.len().saturating_sub(1);
if client.state.trade_ui.inventory_index < max {
client.state.trade_ui.inventory_index += 1;
}
}
}
UiKeyCode::Enter => {
if client.state.trade_ui.picking_inventory {
if let Err(err) = client.trade_confirm_qty_or_present().await {
client.state.push_log(format!("Present failed: {err}"));
}
}
}
_ => {}
}
return true;
}
if client.state.whisper_pouch_ui.open {
let contacts =
flatland_client_lib::contacts_from_stacks(&client.state.whisper_pouch_stacks);
match key.code {
UiKeyCode::Esc => client.state.whisper_pouch_ui.open = false,
UiKeyCode::Up | UiKeyCode::Char('k') => {
if client.state.whisper_pouch_ui.index > 0 {
client.state.whisper_pouch_ui.index -= 1;
}
}
UiKeyCode::Down | UiKeyCode::Char('j') => {
let max = contacts.len().saturating_sub(1);
if client.state.whisper_pouch_ui.index < max {
client.state.whisper_pouch_ui.index += 1;
}
}
UiKeyCode::Enter => {
if let Some(c) = contacts.get(client.state.whisper_pouch_ui.index) {
if !c.blank {
if let Some(peer) = client
.state
.entities
.iter()
.find(|e| e.id != client.state.entity_id && e.label == c.peer_label)
{
client
.state
.social_chat
.open_stone(peer.id, &c.peer_label);
client.state.whisper_pouch_ui.open = false;
} else {
client.state.push_log(format!(
"{} is not online in this region",
c.peer_label
));
}
}
}
}
UiKeyCode::Char('x') | UiKeyCode::Char('d') => {
if let Some(c) = contacts.get(client.state.whisper_pouch_ui.index) {
let id = c.instance_id;
if let Err(err) = client.destroy_whisper_stone(id).await {
client.state.push_log(format!("Destroy stone failed: {err}"));
}
}
}
_ => {}
}
return true;
}
if client.state.bank_panel.is_some() {
match &client.state.bank_ui_mode {
flatland_client_lib::BankUiMode::Menu => match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') | UiKeyCode::Char('c') => {
if let Err(err) = client.close_bank_panel().await {
client.state.push_log(format!("Bank close failed: {err}"));
}
}
UiKeyCode::Up | UiKeyCode::Char('k') => client.bank_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.bank_menu_move(1),
UiKeyCode::Enter | UiKeyCode::Char(' ') => {
if let Err(err) = client.confirm_bank_menu().await {
client.state.push_log(format!("Bank action failed: {err}"));
}
}
UiKeyCode::Char('d') => {
client.state.bank_ui_mode = flatland_client_lib::BankUiMode::DepositAmount {
input: String::new(),
};
}
UiKeyCode::Char('w') => {
client.state.bank_ui_mode = flatland_client_lib::BankUiMode::WithdrawAmount {
input: String::new(),
};
}
UiKeyCode::Char('t') => {
client.state.bank_ui_mode = flatland_client_lib::BankUiMode::TransferName {
input: String::new(),
};
}
_ => {}
},
_ => match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') => client.bank_transfer_back(),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_bank_menu().await {
client.state.push_log(format!("Bank action failed: {err}"));
}
}
UiKeyCode::Backspace => client.bank_transfer_backspace(),
UiKeyCode::Char(c) => client.bank_transfer_append_char(c),
_ => {}
},
}
return true;
}
if client.state.storage_panel.is_some() {
match &client.state.storage_ui_mode {
flatland_client_lib::StorageUiMode::Menu => match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') | UiKeyCode::Char('c') => {
if let Err(err) = client.close_storage_panel().await {
client.state.push_log(format!("Storage close failed: {err}"));
}
}
UiKeyCode::Up | UiKeyCode::Char('k') => client.storage_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.storage_menu_move(1),
UiKeyCode::Enter | UiKeyCode::Char(' ') => {
if let Err(err) = client.confirm_storage_menu().await {
client.state.push_log(format!("Storage action failed: {err}"));
}
}
UiKeyCode::Char('s') => {
let opts = client.state.storage_store_options();
if opts.is_empty() {
client.state.push_log("Nothing loose to store.");
} else {
client.state.storage_ui_mode =
flatland_client_lib::StorageUiMode::StorePick { index: 0 };
}
}
UiKeyCode::Char('t') => {
let opts = client.state.storage_vault_options();
if opts.is_empty() {
client.state.push_log("Vault is empty.");
} else {
client.state.storage_ui_mode =
flatland_client_lib::StorageUiMode::TakePick { index: 0 };
}
}
_ => {}
},
flatland_client_lib::StorageUiMode::StoreAmount { .. }
| flatland_client_lib::StorageUiMode::TakeAmount { .. }
| flatland_client_lib::StorageUiMode::ShipAmount { .. } => match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') => client.storage_ui_back(),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_storage_menu().await {
client.state.push_log(format!("Storage action failed: {err}"));
}
}
UiKeyCode::Backspace => client.storage_amount_backspace(),
UiKeyCode::Char(c) => client.storage_amount_append_char(c),
_ => {}
},
_ => match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') => client.storage_ui_back(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.storage_pick_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.storage_pick_move(1),
UiKeyCode::Enter | UiKeyCode::Char(' ') => {
if let Err(err) = client.confirm_storage_menu().await {
client.state.push_log(format!("Storage action failed: {err}"));
}
}
_ => {}
},
}
return true;
}
if client.state.show_shop_menu {
match key.code {
UiKeyCode::Esc | UiKeyCode::Char('[') | UiKeyCode::Char('n') | UiKeyCode::Char('c') => {
if let Err(err) = client.back_from_shop_menu().await {
client.state.push_log(format!("Shop close failed: {err}"));
}
}
UiKeyCode::Tab => client.shop_tab_toggle(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.shop_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.shop_menu_move(1),
UiKeyCode::PageUp => client.shop_menu_page(-1),
UiKeyCode::PageDown => client.shop_menu_page(1),
UiKeyCode::Char('-') => client.shop_quantity_adjust(-1),
UiKeyCode::Char('=') => client.shop_quantity_adjust(1),
UiKeyCode::Char('a') => client.shop_quantity_set_max(),
UiKeyCode::Enter => {
if let Err(err) = client.shop_confirm().await {
client.state.push_log(format!("Trade failed: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_quest_menu {
match key.code {
UiKeyCode::Esc => {
if client.state.quest_withdraw_confirm {
client.state.quest_withdraw_confirm = false;
} else {
client.state.show_quest_menu = false;
}
}
UiKeyCode::Up | UiKeyCode::Char('k') => client.quest_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.quest_menu_move(1),
UiKeyCode::PageUp => client.quest_menu_page(-1),
UiKeyCode::PageDown => client.quest_menu_page(1),
UiKeyCode::Char('x') => client.quest_request_withdraw(),
UiKeyCode::Enter => {
if let Err(err) = client.quest_confirm_action().await {
client.state.push_log(format!("Quest: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.worker_route_editor.is_some() {
let at_root = client.re_at_root_sheet();
let sheet_index = client.re_sheet_index();
if at_root {
match key.code {
UiKeyCode::Esc => client.close_worker_route_editor(),
UiKeyCode::Char('s') => {
if let Err(err) = client.worker_route_editor_save().await {
client.state.push_log(format!("Route: {err}"));
}
}
UiKeyCode::Down => {
if key.modifiers.contains_control() {
client.worker_route_editor_move_selected(1);
} else {
client.worker_route_editor_select(1);
}
}
UiKeyCode::Up => {
if key.modifiers.contains_control() {
client.worker_route_editor_move_selected(-1);
} else {
client.worker_route_editor_select(-1);
}
}
UiKeyCode::Char('j') => {
if key.modifiers.contains_control() {
client.worker_route_editor_move_selected(-1);
} else {
client.worker_route_editor_select(1);
}
}
UiKeyCode::Char('k') => {
if key.modifiers.contains_control() {
client.worker_route_editor_move_selected(1);
} else {
client.worker_route_editor_select(-1);
}
}
UiKeyCode::Char('d') | UiKeyCode::Delete => {
client.worker_route_editor_delete_selected()
}
UiKeyCode::Char('x') => client.worker_route_editor_clear_stops(),
UiKeyCode::Char('a') => client.re_open_add_menu(),
UiKeyCode::Char('l') => client.re_open_bed_picker(),
UiKeyCode::Char('z') => client.worker_route_editor_toggle_panel(),
UiKeyCode::Enter => client.re_edit_selected_stop(),
_ => {}
}
} else {
match key.code {
UiKeyCode::Esc => client.re_sheet_back(),
UiKeyCode::Char('z') => client.worker_route_editor_toggle_panel(),
UiKeyCode::Char('j') | UiKeyCode::Down => client.re_sheet_move(1),
UiKeyCode::Char('k') | UiKeyCode::Up => client.re_sheet_move(-1),
UiKeyCode::Enter | UiKeyCode::Char(' ') => {
client.re_sheet_row_activate(sheet_index)
}
UiKeyCode::Char('[') | UiKeyCode::Char('-') => client.re_sheet_adjust(-1),
UiKeyCode::Char(']') | UiKeyCode::Char('=') => client.re_sheet_adjust(1),
_ => {}
}
}
return true;
}
if client.state.show_worker_give_picker {
match key.code {
UiKeyCode::Esc => client.close_worker_give_picker(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.worker_give_picker_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.worker_give_picker_move(1),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_worker_give_picker().await {
client.state.push_log(format!("Give: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_worker_give_target_picker {
match key.code {
UiKeyCode::Esc => client.close_worker_give_target_picker(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.worker_give_target_picker_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.worker_give_target_picker_move(1),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_worker_give_target_picker().await {
client.state.push_log(format!("Give: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_worker_take_picker {
match key.code {
UiKeyCode::Esc => client.close_worker_take_picker(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.worker_take_picker_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.worker_take_picker_move(1),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_worker_take_picker().await {
client.state.push_log(format!("Take: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_worker_teach_picker {
match key.code {
UiKeyCode::Esc => client.close_worker_teach_picker(),
UiKeyCode::Up | UiKeyCode::Char('k') => client.worker_teach_picker_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.worker_teach_picker_move(1),
UiKeyCode::Enter => {
if let Err(err) = client.confirm_worker_teach_picker().await {
client.state.push_log(format!("Teach: {err}"));
}
}
_ => {}
}
return true;
}
if client.state.show_workers_menu {
match key.code {
UiKeyCode::Esc => client.state.show_workers_menu = false,
UiKeyCode::Up | UiKeyCode::Char('k') => client.workers_menu_move(-1),
UiKeyCode::Down | UiKeyCode::Char('j') => client.workers_menu_move(1),
UiKeyCode::PageUp => client.workers_menu_page(-1),
UiKeyCode::PageDown => client.workers_menu_page(1),
UiKeyCode::Char('d') => {
if let Err(err) = client.workers_dismiss_selected().await {
client.state.push_log(format!("Worker: {err}"));
}
}
UiKeyCode::Char('r') => {
if let Err(err) = client.hire_worker_laborer().await {
client.state.push_log(format!("Hire: {err}"));
}
}
UiKeyCode::Char('e') => {
if let Err(err) = client.open_worker_route_editor_for_selected() {
client.state.push_log(format!("Route: {err}"));
}
}
UiKeyCode::Char('n') => {
if let Err(err) = client.open_worker_rename() {
client.state.push_log(format!("Rename: {err}"));
}
}
UiKeyCode::Char('g') => {
if let Err(err) = client.open_worker_give_picker() {
client.state.push_log(format!("Give: {err}"));
}
}
UiKeyCode::Char('i') => {
if let Err(err) = client.open_worker_take_picker() {
client.state.push_log(format!("Take: {err}"));
}
}
UiKeyCode::Char('t') => {
if let Err(err) = client.open_worker_teach_picker() {
client.state.push_log(format!("Teach: {err}"));
}
}
UiKeyCode::Char('c') => client.toggle_workers_menu_compact(),
UiKeyCode::Enter => {
if let Err(err) = client.workers_confirm_action().await {
client.state.push_log(format!("Worker: {err}"));
}
}
_ => {}
}
return true;
}
false
}
pub async fn dispatch_action<S: PlayConnection>(
client: &mut GameClient<S>,
_keys: &ClientKeyBindings,
action: InputAction,
ctx: &mut ActionCtx<'_>,
) {
match action {
InputAction::StartChat { whisper } => {
if whisper {
client.state.whisper_pouch_ui.open = false;
client.state.social_chat.picking_stone = true;
client.state.social_chat.stone_pick_index = 0;
client.state.social_chat.input_focused = false;
client.state.social_chat.push_system(
"Whisper stones — ↑↓ select · Enter · Esc cancel",
);
} else {
client.state.social_chat.focus_nearby();
}
}
InputAction::Harvest => {
if let Err(err) = client.harvest_nearest().await {
client.state.push_log(format!("Harvest failed: {err}"));
}
}
InputAction::Pickup => {
if client.pickup_nearest().await.is_err() {
if let Err(err) = client.pickup_nearest_container().await {
client.state.push_log(format!("Pickup: {err}"));
}
}
}
InputAction::Craft => client.open_craft_menu(),
InputAction::Interact | InputAction::UseWorld => {
if let Err(err) = client.use_nearest().await {
client.state.push_log(format!("Use: {err}"));
}
}
InputAction::TestDamage => {
if let Err(err) = client.test_damage(25.0).await {
client.state.push_log(format!("Damage failed: {err}"));
}
}
InputAction::CycleCombatTarget { reverse } => {
if let Err(err) = client.cycle_combat_target(reverse).await {
client.state.push_log(format!("T1 target: {err}"));
}
}
InputAction::CycleCombatTargetT2 { reverse } => {
if let Err(err) = client.cycle_combat_target_slot(2, reverse).await {
client.state.push_log(format!("T2 target: {err}"));
}
}
InputAction::AdvanceRotationT1 => {
if let Err(err) = client.advance_rotation(1).await {
client.state.push_log(format!("T1 step: {err}"));
}
}
InputAction::AdvanceRotationT2 => {
if let Err(err) = client.advance_rotation(2).await {
client.state.push_log(format!("T2 step: {err}"));
}
}
InputAction::ToggleAutoT1 => {
if let Err(err) = client.toggle_auto_attack_slot(1).await {
client.state.push_log(format!("T1 auto: {err}"));
}
}
InputAction::ToggleAutoT2 => {
if let Err(err) = client.toggle_auto_attack_slot(2).await {
client.state.push_log(format!("T2 auto: {err}"));
}
}
InputAction::ToggleLoadout => {
client.state.show_rotation_editor = false;
client.state.rotation_editor.reset();
client.state.show_loadout_menu = !client.state.show_loadout_menu;
if client.state.show_loadout_menu {
client.state.loadout_focus_presets = true;
client.state.loadout_hotbar_slot = client.state.loadout_hotbar_slot.clamp(1, 9);
let ability_len = client.state.loadout_hotbar_choices().len();
if ability_len > 0 {
client.state.loadout_ability_index =
client.state.loadout_ability_index.min(ability_len - 1);
} else {
client.state.loadout_ability_index = 0;
}
let preset_len = client.state.rotation_presets.len();
if preset_len > 0 {
client.state.loadout_menu_index =
client.state.loadout_menu_index.min(preset_len - 1);
} else {
client.state.loadout_menu_index = 0;
}
*ctx.auto_nav = None;
let _ = client.stop().await;
}
}
InputAction::ToggleRotationEditor => {
client.state.show_loadout_menu = false;
if client.state.show_rotation_editor {
client.state.show_rotation_editor = false;
client.state.rotation_editor.reset();
} else {
client.state.show_rotation_editor = true;
client.state.rotation_editor.reset();
let max = client.state.rotation_presets.len();
if max > 0 {
client.state.rotation_editor.list_index =
client.state.rotation_editor.list_index.min(max - 1);
}
}
if client.state.show_rotation_editor {
*ctx.auto_nav = None;
let _ = client.stop().await;
}
}
InputAction::LoadoutMenuUp => {
if !client.state.show_loadout_menu {
return;
}
if client.state.loadout_focus_presets {
if client.state.loadout_menu_index > 0 {
client.state.loadout_menu_index -= 1;
}
} else if client.state.loadout_ability_index > 0 {
client.state.loadout_ability_index -= 1;
}
}
InputAction::LoadoutMenuDown => {
if !client.state.show_loadout_menu {
return;
}
if client.state.loadout_focus_presets {
let max = client.state.rotation_presets.len();
if max > 0 {
client.state.loadout_menu_index =
(client.state.loadout_menu_index + 1).min(max - 1);
}
} else {
let max = client.state.loadout_hotbar_choices().len();
if max > 0 {
client.state.loadout_ability_index =
(client.state.loadout_ability_index + 1).min(max - 1);
}
}
}
InputAction::LoadoutHotbarPrev => {
if client.state.show_loadout_menu {
let slot = client.state.loadout_hotbar_slot;
client.state.loadout_hotbar_slot = if slot <= 1 { 9 } else { slot - 1 };
}
}
InputAction::LoadoutHotbarNext => {
if client.state.show_loadout_menu {
let slot = client.state.loadout_hotbar_slot;
client.state.loadout_hotbar_slot = if slot >= 9 { 1 } else { slot + 1 };
}
}
InputAction::LoadoutToggleFocus => {
if client.state.show_loadout_menu {
client.state.loadout_focus_presets = !client.state.loadout_focus_presets;
}
}
InputAction::LoadoutBindHotbar => {
if !client.state.show_loadout_menu {
return;
}
let slot = client.state.loadout_hotbar_slot;
let binding = {
let choices = client.state.loadout_hotbar_choices();
choices
.get(client.state.loadout_ability_index)
.map(|c| c.binding.clone())
};
if let Some(binding) = binding {
if let Err(err) = client.set_hotbar_slot(slot, Some(&binding)).await {
client.state.push_log(format!("Hotbar: {err}"));
}
} else {
client.state.push_log("No ability/consumable selected to bind");
}
}
InputAction::LoadoutClearHotbar => {
if !client.state.show_loadout_menu {
return;
}
let slot = client.state.loadout_hotbar_slot;
if let Err(err) = client.set_hotbar_slot(slot, None).await {
client.state.push_log(format!("Hotbar: {err}"));
}
}
InputAction::LoadoutAssignT1 => {
if !client.state.show_loadout_menu {
return;
}
let preset = {
let idx = client.state.loadout_menu_index;
client.state.rotation_presets.get(idx).cloned()
};
if let Some(preset) = preset {
let already = client
.state
.combat_slots
.iter()
.find(|s| s.slot_index == 1)
.and_then(|s| s.preset_id.as_deref())
== Some(preset.id.as_str());
if already {
client.state.push_log(format!(
"T1 already uses {} (equip weapons from inventory — Weapon auto tracks mainhand)",
preset.label
));
} else if let Err(err) = client.assign_slot_preset(1, &preset.id).await {
client.state.push_log(format!("Loadout: {err}"));
} else {
client
.state
.push_log(format!("T1 ← {}", preset.label));
}
}
}
InputAction::LoadoutAssignT2 => {
if !client.state.show_loadout_menu {
return;
}
let preset = {
let idx = client.state.loadout_menu_index;
client.state.rotation_presets.get(idx).cloned()
};
if let Some(preset) = preset {
let already = client
.state
.combat_slots
.iter()
.find(|s| s.slot_index == 2)
.and_then(|s| s.preset_id.as_deref())
== Some(preset.id.as_str());
if already {
client
.state
.push_log(format!("T2 already uses {}", preset.label));
} else if let Err(err) = client.assign_slot_preset(2, &preset.id).await {
client.state.push_log(format!("Loadout: {err}"));
} else {
client
.state
.push_log(format!("T2 ← {}", preset.label));
}
}
}
InputAction::CloseOverlay => {
client.state.show_loadout_menu = false;
client.state.show_rotation_editor = false;
client.state.rotation_editor.reset();
}
InputAction::ClearCombatTarget => {
if let Err(err) = client.clear_combat_target().await {
client.state.push_log(format!("Target: {err}"));
}
}
InputAction::ClearCombatTargetT2 => {
if let Err(err) = client.clear_combat_target_slot(2).await {
client.state.push_log(format!("T2 target: {err}"));
}
}
InputAction::Dodge => {
*ctx.auto_nav = None;
if let Err(err) = client.dodge().await {
client.state.push_log(format!("Dodge: {err}"));
}
}
InputAction::Lunge => {
*ctx.auto_nav = None;
let _ = client.stop().await;
if let Err(err) = client.lunge().await {
client.state.push_log(format!("Lunge: {err}"));
}
}
InputAction::DirectionalJump { forward, strafe } => {
*ctx.auto_nav = None;
let _ = client.stop().await;
if let Err(err) = client.directional_jump(forward, strafe).await {
client.state.push_log(format!("Jump: {err}"));
}
}
InputAction::CastHotbar { slot } => {
if let Err(err) = client.cast_hotbar_ability(slot).await {
client.state.push_log(format!("Hotbar {slot}: {err}"));
}
}
InputAction::ToggleBlock => {
*ctx.block_active = !*ctx.block_active;
if let Err(err) = client.set_block(*ctx.block_active).await {
client.state.push_log(format!("Block: {err}"));
*ctx.block_active = false;
}
}
InputAction::ToggleStats => client.toggle_stats(),
InputAction::ToggleEquip => client.toggle_equip_menu(),
InputAction::CycleCharacterSheetTab => client.cycle_character_sheet_tab(),
InputAction::LedgerPeriodDigit(c) => client.set_ledger_period_digit(c),
InputAction::ToggleInventory => client.toggle_inventory_menu(),
InputAction::ToggleKeychain => client.toggle_keychain_menu(),
InputAction::ToggleQuestMenu => client.toggle_quest_menu(),
InputAction::ToggleWorkersMenu => client.toggle_workers_menu(),
InputAction::QuestMenuUp => client.quest_menu_move(-1),
InputAction::QuestMenuDown => client.quest_menu_move(1),
InputAction::QuestWithdraw => client.quest_request_withdraw(),
InputAction::RotationEditorBack => {
let _ = client.back_on_esc();
}
InputAction::RotationEditorListUp
| InputAction::RotationEditorListDown
| InputAction::RotationEditorEdit
| InputAction::RotationEditorNew
| InputAction::RotationEditorDelete
| InputAction::RotationEditorAddAbility
| InputAction::RotationEditorRemoveAbility
| InputAction::RotationEditorMoveAbilityUp
| InputAction::RotationEditorMoveAbilityDown
| InputAction::RotationEditorAbilityUp
| InputAction::RotationEditorAbilityDown
| InputAction::RotationEditorPickerUp
| InputAction::RotationEditorPickerDown
| InputAction::RotationEditorPickAbility
| InputAction::RotationEditorRename
| InputAction::RotationEditorConfirmLabel
| InputAction::RotationEditorLabelBackspace
| InputAction::RotationEditorLabelChar(_)
| InputAction::RotationEditorSave => {
handle_rotation_editor_action(client, action).await;
}
InputAction::None
| InputAction::Quit
| InputAction::ToggleHelp
| InputAction::CycleHudView
| InputAction::SubmitChat
| InputAction::CancelChat
| InputAction::ToggleSprintMode
| InputAction::ToggleMapTarget
| InputAction::ConfirmMapTarget
| InputAction::CancelMapTarget
| InputAction::MapTargetNudge { .. }
| InputAction::CancelAutoNav
| InputAction::StopMovement => {}
InputAction::ToggleHudLog => {
client.state.hud_log_hidden = !client.state.hud_log_hidden;
let mut cfg = flatland_client_lib::ClientConfig::load();
let _ = cfg.save_hud_log_hidden(client.state.hud_log_hidden);
if client.state.hud_log_hidden {
client.state.push_log("LOG hidden — press ' to show");
} else {
client.state.push_log("LOG shown — press ' to hide");
}
}
}
}
async fn handle_rotation_editor_action<S: PlayConnection>(
client: &mut GameClient<S>,
action: InputAction,
) {
match action {
InputAction::RotationEditorListUp => {
if client.state.rotation_editor.list_index > 0 {
client.state.rotation_editor.list_index -= 1;
}
}
InputAction::RotationEditorListDown => {
let max = client.state.rotation_presets.len();
if max > 0 {
client.state.rotation_editor.list_index =
(client.state.rotation_editor.list_index + 1).min(max - 1);
}
}
InputAction::RotationEditorEdit => {
let idx = client.state.rotation_editor.list_index;
if let Some(preset) = client.state.rotation_presets.get(idx).cloned() {
client.state.rotation_editor.draft = Some(preset);
client.state.rotation_editor.ability_index = 0;
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
}
InputAction::RotationEditorNew => {
let preset = next_custom_preset(&client.state.rotation_presets);
client.state.rotation_editor.list_index = client.state.rotation_presets.len();
client.state.rotation_editor.draft = Some(preset);
client.state.rotation_editor.ability_index = 0;
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
InputAction::RotationEditorDelete => {
let idx = client.state.rotation_editor.list_index;
let preset_id = client.state.rotation_presets.get(idx).map(|p| p.id.clone());
if let Some(id) = preset_id {
if preset_deletable(&id) {
if let Err(err) = client.delete_rotation_preset(&id).await {
client.state.push_log(format!("Delete: {err}"));
} else {
let max = client.state.rotation_presets.len();
client.state.rotation_editor.list_index = if max == 0 {
0
} else {
client.state.rotation_editor.list_index.min(max - 1)
};
}
} else {
client.state.push_log("Cannot delete built-in preset");
}
}
}
InputAction::RotationEditorBack => match client.state.rotation_editor.mode {
RotationEditorMode::EditLabel => {
client.state.rotation_editor.label_buffer.clear();
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
RotationEditorMode::PickAbility => {
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
RotationEditorMode::EditSequence => {
client.state.rotation_editor.draft = None;
client.state.rotation_editor.mode = RotationEditorMode::List;
}
RotationEditorMode::List => {}
},
InputAction::RotationEditorAddAbility => {
client.state.rotation_editor.picker_index = 0;
client.state.rotation_editor.mode = RotationEditorMode::PickAbility;
}
InputAction::RotationEditorRemoveAbility => {
let idx = client.state.rotation_editor.ability_index;
if let Some(draft) = &mut client.state.rotation_editor.draft {
if idx < draft.abilities.len() {
draft.abilities.remove(idx);
if client.state.rotation_editor.ability_index >= draft.abilities.len()
&& client.state.rotation_editor.ability_index > 0
{
client.state.rotation_editor.ability_index -= 1;
}
}
}
}
InputAction::RotationEditorMoveAbilityUp => {
let i = client.state.rotation_editor.ability_index;
if let Some(draft) = &mut client.state.rotation_editor.draft {
if i > 0 && i < draft.abilities.len() {
draft.abilities.swap(i, i - 1);
client.state.rotation_editor.ability_index -= 1;
}
}
}
InputAction::RotationEditorMoveAbilityDown => {
let i = client.state.rotation_editor.ability_index;
if let Some(draft) = &mut client.state.rotation_editor.draft {
if i + 1 < draft.abilities.len() {
draft.abilities.swap(i, i + 1);
client.state.rotation_editor.ability_index += 1;
}
}
}
InputAction::RotationEditorAbilityUp => {
if client.state.rotation_editor.ability_index > 0 {
client.state.rotation_editor.ability_index -= 1;
}
}
InputAction::RotationEditorAbilityDown => {
if let Some(draft) = &client.state.rotation_editor.draft {
if !draft.abilities.is_empty() {
client.state.rotation_editor.ability_index =
(client.state.rotation_editor.ability_index + 1)
.min(draft.abilities.len() - 1);
}
}
}
InputAction::RotationEditorPickerUp => {
if client.state.rotation_editor.picker_index > 0 {
client.state.rotation_editor.picker_index -= 1;
}
}
InputAction::RotationEditorPickerDown => {
let choices = editor_ability_choices(
&client.state.known_abilities,
&client.state.weapon_ability_id,
);
if !choices.is_empty() {
client.state.rotation_editor.picker_index =
(client.state.rotation_editor.picker_index + 1).min(choices.len() - 1);
}
}
InputAction::RotationEditorPickAbility => {
let choices = editor_ability_choices(
&client.state.known_abilities,
&client.state.weapon_ability_id,
);
let pick = client.state.rotation_editor.picker_index;
if let Some(ability) = choices.get(pick) {
if let Some(draft) = &mut client.state.rotation_editor.draft {
let max = client.state.max_abilities_per_rotation.max(1) as usize;
if draft.abilities.len() >= max {
client
.state
.push_log(format!("Rotation full (max {max} from INT+WIS)"));
} else {
draft.abilities.push(ability.clone());
client.state.rotation_editor.ability_index =
draft.abilities.len().saturating_sub(1);
}
}
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
}
InputAction::RotationEditorRename => {
if let Some(draft) = &client.state.rotation_editor.draft {
client.state.rotation_editor.label_buffer = draft.label.clone();
client.state.rotation_editor.mode = RotationEditorMode::EditLabel;
}
}
InputAction::RotationEditorConfirmLabel => {
let label = client.state.rotation_editor.label_buffer.trim().to_string();
if label.is_empty() {
client.state.push_log("Label cannot be empty");
} else if let Some(draft) = &mut client.state.rotation_editor.draft {
draft.label = label;
client.state.rotation_editor.label_buffer.clear();
client.state.rotation_editor.mode = RotationEditorMode::EditSequence;
}
}
InputAction::RotationEditorLabelBackspace => {
client.state.rotation_editor.label_buffer.pop();
}
InputAction::RotationEditorLabelChar(c) => {
if client.state.rotation_editor.label_buffer.len() < 32 {
client.state.rotation_editor.label_buffer.push(c);
}
}
InputAction::RotationEditorSave => {
let draft = match client.state.rotation_editor.draft.clone() {
Some(d) => d,
None => return,
};
if draft.abilities.is_empty() {
client.state.push_log("Rotation needs at least one ability");
return;
}
let saved_id = draft.id.clone();
if let Err(err) = client.upsert_rotation_preset(draft).await {
client.state.push_log(format!("Save: {err}"));
} else {
client.state.rotation_editor.mode = RotationEditorMode::List;
client.state.rotation_editor.draft = None;
if let Some(i) = client
.state
.rotation_presets
.iter()
.position(|p| p.id == saved_id)
{
client.state.rotation_editor.list_index = i;
}
}
}
_ => {}
}
}