From 6206de8cf7cb0af9b132ae7871d8181b46af5c2f Mon Sep 17 00:00:00 2001 From: Dmytro Lysai Date: Mon, 6 Jul 2020 17:57:50 +0300 Subject: [PATCH] [game,inventory] Add weapon loading --- src/asset/frame/id/consts.rs | 14 +- src/asset/proto.rs | 4 +- src/asset/proto/db.rs | 4 +- src/asset/proto/id.rs | 54 +------ src/game/inventory.rs | 284 ++++++++++++++++++++++++----------- src/game/object.rs | 78 ++++++++++ src/game/skilldex.rs | 2 +- src/game/state.rs | 3 +- src/game/ui.rs | 1 + src/game/ui/action_menu.rs | 2 +- src/game/ui/move_window.rs | 114 ++++++++++++++ src/ui/button.rs | 2 +- src/ui/command.rs | 16 +- src/ui/image_text.rs | 4 + 14 files changed, 424 insertions(+), 158 deletions(-) create mode 100644 src/game/ui/move_window.rs diff --git a/src/asset/frame/id/consts.rs b/src/asset/frame/id/consts.rs index f79b1f5..15c6348 100644 --- a/src/asset/frame/id/consts.rs +++ b/src/asset/frame/id/consts.rs @@ -192,10 +192,10 @@ impl FrameId { pub const SEXOFF: FrameId = FrameId::Generic(Generic(0x6000_0bc)); pub const SEXON: FrameId = FrameId::Generic(Generic(0x6000_0bd)); pub const SLIDER: FrameId = FrameId::Generic(Generic(0x6000_0be)); - pub const SNEGOFF: FrameId = FrameId::Generic(Generic(0x6000_0bf)); - pub const SNEGON: FrameId = FrameId::Generic(Generic(0x6000_0c0)); - pub const SPLSOFF: FrameId = FrameId::Generic(Generic(0x6000_0c1)); - pub const SPLSON: FrameId = FrameId::Generic(Generic(0x6000_0c2)); + pub const BUTTON_MINUS_UP: FrameId = FrameId::Generic(Generic(0x6000_0bf)); + pub const BUTTON_MINUS_DOWN: FrameId = FrameId::Generic(Generic(0x6000_0c0)); + pub const BUTTON_PLUS_UP: FrameId = FrameId::Generic(Generic(0x6000_0c1)); + pub const BUTTON_PLUS_DOWN: FrameId = FrameId::Generic(Generic(0x6000_0c2)); pub const STNEGOFF: FrameId = FrameId::Generic(Generic(0x6000_0c3)); pub const STNEGON: FrameId = FrameId::Generic(Generic(0x6000_0c4)); pub const STPLSOFF: FrameId = FrameId::Generic(Generic(0x6000_0c5)); @@ -306,10 +306,10 @@ impl FrameId { pub const UNLOADN: FrameId = FrameId::Generic(Generic(0x6000_12e)); pub const SKILLH: FrameId = FrameId::Generic(Generic(0x6000_12f)); pub const SKILLN: FrameId = FrameId::Generic(Generic(0x6000_130)); - pub const MOVEMULT: FrameId = FrameId::Generic(Generic(0x6000_131)); + pub const INVENTORY_MOVE_MULTIPLE_WINDOW: FrameId = FrameId::Generic(Generic(0x6000_131)); pub const TIMER: FrameId = FrameId::Generic(Generic(0x6000_132)); - pub const ALLBON: FrameId = FrameId::Generic(Generic(0x6000_133)); - pub const ALLBOFF: FrameId = FrameId::Generic(Generic(0x6000_134)); + pub const BUTTON_ALL_UP: FrameId = FrameId::Generic(Generic(0x6000_133)); + pub const BUTTON_ALL_DOWN: FrameId = FrameId::Generic(Generic(0x6000_134)); pub const DEATH: FrameId = FrameId::Generic(Generic(0x6000_135)); pub const WATCH: FrameId = FrameId::Generic(Generic(0x6000_136)); pub const SEQ2AD: FrameId = FrameId::Generic(Generic(0x6000_137)); diff --git a/src/asset/proto.rs b/src/asset/proto.rs index 1bb628b..1ae4610 100644 --- a/src/asset/proto.rs +++ b/src/asset/proto.rs @@ -270,7 +270,7 @@ pub struct Weapon { // Number of bullets per burst shot. pub burst_bullet_count: i32, // proto.msg:300 - pub caliber: i32, + pub caliber: u32, pub ammo_proto_id: Option, /// Magazine capacity. pub max_ammo_count: u32, @@ -279,7 +279,7 @@ pub struct Weapon { #[derive(Debug)] pub struct Ammo { - pub caliber: i32, + pub caliber: u32, pub max_ammo_count: u32, pub ac_modifier: i32, pub dr_modifier: i32, diff --git a/src/asset/proto/db.rs b/src/asset/proto/db.rs index eea862a..fc9a49a 100644 --- a/src/asset/proto/db.rs +++ b/src/asset/proto/db.rs @@ -345,7 +345,7 @@ impl ProtoDb { let crit_failure_table = rd.read_i32::()?; let perk = read_opt_enum(rd, "invalid weapon perk")?; let burst_bullet_count = rd.read_i32::()?; - let caliber = rd.read_i32::()?; + let caliber = rd.read_u32::()?; let ammo_proto_id = ProtoId::read_opt(rd)?; let max_ammo_count = rd.read_i32::()?.try_into().unwrap(); let sound_id = rd.read_u8()?; @@ -370,7 +370,7 @@ impl ProtoDb { } fn read_ammo(rd: &mut impl Read) -> io::Result { - let caliber = rd.read_i32::()?; + let caliber = rd.read_u32::()?; let max_ammo_count = rd.read_i32::()?.try_into().unwrap(); let ac_modifier = rd.read_i32::()?; let dr_modifier = rd.read_i32::()?; diff --git a/src/asset/proto/id.rs b/src/asset/proto/id.rs index 069641a..f57995f 100644 --- a/src/asset/proto/id.rs +++ b/src/asset/proto/id.rs @@ -5,59 +5,6 @@ use std::io::{self, Error, ErrorKind, prelude::*}; use crate::asset::EntityKind; -/* PID_ROCK = 0x13, - PID_SMALL_ENERGY_CELL = 0x26, - PID_MICRO_FUSION_CELL = 0x27, - PID_STIMPAK = 0x28, - PID_BOTTLE_CAPS = 0x29, - PID_FIRST_AID_KIT = 0x2F, - PID_ANTIDOTE = 0x31, - PID_DYNAMITE = 0x33, - PID_GEIGER_COUNTER = 0x34, - PID_MENTATS = 0x35, - PID_STEALTH_BOY = 0x36, - PID_WATER_CHIP = 0x37, - PID_HOLODISK = 0x3A, - PID_MOTION_SENSOR = 0x3B, - PID_MUTATED_FRUIT = 0x47, - PID_BIG_BOOK_OF_SCIENCE = 0x49, - PID_DEANS_ELECTRONICS = 0x4C, - PID_FLARE = 0x4F, - PID_FIRST_AID_BOOK = 0x50, - PID_PLASTIC_EXPLOSIVES = 0x55, - PID_SCOUT_HANDBOOK = 0x56, - PID_BUFFOUT = 0x57, - PID_DOCTORS_BAG = 0x5B, - PID_PUMP_PARTS = 0x62, - PID_GUNS_AND_BULLETS = 0x66, - PID_NUKA_COLA = 0x6A, - PID_RAD_X = 0x6D, - PID_PSYCHO = 0x6E, - PID_SUPER_STIMPAK = 0x90, - PID_ACTIVE_FLARE = 0xCD, - PID_ACTIVE_DYNAMITE = 0xCE, - PID_ACTIVE_GEIGER_COUNTER = 0xCF, - PID_ACTIVE_MOTION_SENSOR = 0xD0, - PID_ACTIVE_PLASTIC_EXPLOSIVE = 0xD1, - PID_ACTIVE_STEALTH_BOY = 0xD2, - PID_TECHNICAL_MANUAL = 0xE4, - PID_CHEMISTRY_MANUAL = 0xED, - PID_JET = 0x103, - PID_JET_ANTIDOTE = 0x104, - PID_GECK = 0x16E, - PID_CAR_TRUNK = 0x1C7, - PID_JESSE_CONTAINER = 0x1D3, - PID_DUDE = 0x1000000, - PID_DRIVABLE_CAR = 0x20003F1, - PID_NULL = 0xFFFFFFFF, - - PID_HARDENED_POWER_ARMOR = 0xE8, - PID_ADVANCED_POWER_ARMOR = 0x15C, - PID_ADVANCED_POWER_ARMOR_MK2 = 0x15D, - PID_POWER_ARMOR = 0x3, - PID_MIRRORED_SHADES = 0x1B1, - PID_SCROLL_BLOCKER = 0x500000C,*/ - #[derive(Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd)] pub struct ProtoId(u32); @@ -78,6 +25,7 @@ impl ProtoId { pub const ACTIVE_PLASTIC_EXPLOSIVE: Self = unsafe { Self::from_packed_unchecked(0xD1) }; pub const SCROLL_BLOCKER: Self = unsafe { Self::from_packed_unchecked(0x0500000c) }; pub const BOTTLE_CAPS: Self = unsafe { Self::from_packed_unchecked(0x29) }; + pub const SOLAR_SCORCHER: Self = unsafe { Self::from_packed_unchecked(390) }; pub fn new(kind: EntityKind, id: u32) -> Option { if id <= 0xffffff { diff --git a/src/game/inventory.rs b/src/game/inventory.rs index 7693ac1..955ad37 100644 --- a/src/game/inventory.rs +++ b/src/game/inventory.rs @@ -1,6 +1,7 @@ use bstring::{bstr, BString}; use bstring::bfmt::ToBString; use if_chain::if_chain; +use sdl2::mouse::MouseButton; use crate::asset::*; use crate::asset::frame::FrameId; @@ -10,6 +11,7 @@ use crate::game::object::{self, EquipmentSlot, Hand, Object, InventoryItem}; use crate::game::rpg::Rpg; use crate::game::ui::action_menu::{self, Action}; use crate::game::ui::inventory_list::{self, InventoryList, Scroll, MouseMode}; +use crate::game::ui::move_window::MoveWindow; use crate::game::world::WorldRef; use crate::graphics::{Point, Rect}; use crate::graphics::color::{GREEN, RED}; @@ -17,11 +19,10 @@ use crate::graphics::font::*; use crate::graphics::sprite::Sprite; use crate::ui::{self, Ui, button, Widget, HandleEvent, Cursor}; use crate::ui::button::Button; -use crate::ui::command::{UiCommandData, UiCommand}; +use crate::ui::command::{move_window, UiCommand, UiCommandData}; use crate::ui::command::inventory::Command; use crate::ui::panel::{self, Panel}; use crate::util::sprintf; -use sdl2::mouse::MouseButton; const MSG_NO_ITEM: MessageId = 14; const MSG_DMG: MessageId = 15; @@ -56,40 +57,15 @@ impl Inventory { match c { Command::Show => { self.show(rpg, ui); - return; } Command::Hide => { self.hide(ui); - return; } _ => {} } - let internal = self.internal.as_mut().unwrap(); - match c { - Command::Show | Command::Hide => unreachable!(), - Command::Scroll(scroll) => { - internal.scroll(scroll, ui); - } - Command::Hover { .. } => {} - Command::ActionMenu { object } => { - internal.show_action_menu(object, ui); - } - Command::Action { object, action } => { - internal.hide_action_menu(ui); - match action { - Some(Action::Unload) => { - internal.unload(object, rpg, ui); - } - _ => {} - } - } - Command::ListDrop { pos, object } => { - internal.handle_list_drop(cmd.source, pos, object, rpg, ui); - } - Command::ToggleMouseMode => { - internal.toggle_mouse_mode(rpg, ui); - } - } + } + if let Some(v) = self.internal.as_mut() { + v.handle(cmd, rpg, ui); } } @@ -101,7 +77,7 @@ impl Inventory { let owner = self.world.borrow().objects().dude(); let internal = Internal::new(self.msgs.take().unwrap(), self.world.clone(), owner, ui); internal.sync_mouse_mode_to_ui(ui); - internal.sync_from_obj(rpg, ui); + internal.sync_to_ui(rpg, ui); assert!(self.internal.replace(internal).is_none()); } @@ -139,6 +115,7 @@ struct Internal { total_weight: ui::Handle, action_menu: Option, + move_window: Option, } impl Internal { @@ -247,6 +224,7 @@ impl Internal { stat_columns, total_weight, action_menu: None, + move_window: None, } } @@ -254,7 +232,7 @@ impl Internal { ui.remove(self.win); } - fn sync_from_obj(&self, rpg: &Rpg, ui: &Ui) { + fn sync_to_ui(&self, rpg: &Rpg, ui: &Ui) { let list = &mut ui.widget_mut::(self.list); let wearing = &mut ui.widget_mut::(self.wearing); let left_hand = &mut ui.widget_mut::(self.left_hand); @@ -297,12 +275,7 @@ impl Internal { // display_inventory_info fn make_list_item(item: &InventoryItem, obj: &Object) -> inventory_list::Item { let proto = obj.proto().unwrap(); - let count = if obj.item_kind() == Some(ItemKind::Ammo) { - obj.proto().unwrap().max_ammo_count().unwrap() * (item.count - 1) - + obj.ammo_count().unwrap() - } else { - item.count - }; + let count = obj.total_ammo_count(item.count).unwrap_or(item.count); inventory_list::Item { object: item.object, fid: proto.sub.as_item().unwrap().inventory_fid.unwrap(), @@ -310,7 +283,7 @@ impl Internal { } } - fn scroll(&self, scroll: Scroll, ui: &mut Ui) { + fn scroll(&self, scroll: Scroll, ui: &Ui) { let list = &mut ui.widget_mut::(self.list); list.scroll(scroll); self.update_list_scroll_buttons(list, ui); @@ -582,12 +555,12 @@ impl Internal { } // switch_hands - fn handle_list_drop(&self, + fn handle_list_drop(&mut self, src: ui::Handle, pos: Point, - obj: object::Handle, + src_obj: object::Handle, rpg: &Rpg, - ui: &Ui, + ui: &mut Ui, ) { let src_slot = self.slot_from_widget(src).unwrap(); @@ -597,67 +570,136 @@ impl Internal { } let target_slot = unwrap_or_return!(self.slot_from_widget(target), Some); + assert_ne!(src_slot, target_slot); + let world = self.world.borrow(); - let (bump, existing) = { - let (bump, existing) = match target_slot { - Slot::Inventory => (Some(obj), None), - Slot::Equipment(eq_slot) => { - let v = world.objects().get(self.owner) - .equipment(eq_slot, world.objects()); - (v, v) - } - }; - match target_slot { - Slot::Equipment(target_slot) => { - let mut obj = world.objects().get_mut(obj); + enum Action { + MoveTo { + item: object::Handle, + slot: Slot, + }, + Reload { + weapon: object::Handle, + max_count: u32, + }, + ArmorChange { + old_armor: Option, + new_armor: Option, + }, + } - if target_slot == EquipmentSlot::Armor - && obj.proto().unwrap().kind() != ExactEntityKind::Item(ItemKind::Armor) - { + let mut actions = Vec::new(); + + match target_slot { + Slot::Inventory => { + actions.push(Action::MoveTo { + item: src_obj, + slot: Slot::Inventory, + }); + } + Slot::Equipment(eq_slot) => { + let owner = world.objects().get(self.owner); + let target_obj = owner.equipment(eq_slot, world.objects()); + let src_obj = world.objects().get(src_obj); + + if eq_slot == EquipmentSlot::Armor { + if src_obj.proto().unwrap().kind() != ExactEntityKind::Item(ItemKind::Armor) { return; } + actions.push(Action::ArmorChange { + old_armor: target_obj, + new_armor: Some(src_obj.handle()), + }); + } - obj.flags.remove(Flag::Worn | Flag::LeftHand | Flag::RightHand); - - match target_slot { - EquipmentSlot::Armor => obj.flags.insert(Flag::Worn), - EquipmentSlot::Hand(Hand::Left) => obj.flags.insert(Flag::LeftHand), - EquipmentSlot::Hand(Hand::Right) => obj.flags.insert(Flag::RightHand), + // Check for weapon reload. + let reload = if_chain! { + if let Some(target_obj) = target_obj; + let weapon = world.objects().get(target_obj); + if let Some(max_count) = weapon.can_reload_weapon(&src_obj); + then { + actions.push(Action::Reload { + weapon: weapon.handle(), + max_count, + }); + true + } else { + false + } + }; + + // Default actions are to move/replace. + if !reload { + actions.push(Action::MoveTo { + item: src_obj.handle(), + slot: Slot::Equipment(eq_slot), + }); + if let Some(target_obj) = target_obj { + // Move target out of the slot. + actions.push(Action::MoveTo { + item: target_obj, + slot: Slot::Inventory, + }); } } - Slot::Inventory => {} } - - (bump, existing) }; - - // Bump item: remove from slots and move to inventory top. - if let Some(bump) = bump { - let mut owner = world.objects().get_mut(self.owner); - world.objects().get_mut(bump) - .flags.remove(Flag::Worn | Flag::LeftHand | Flag::RightHand); - let i = owner.inventory.items.iter() - .position(|i| i.object == bump).unwrap(); - if i > 0 { - let item = owner.inventory.items.remove(i); - owner.inventory.items.insert(0, item); - } + if src_slot == Slot::Equipment(EquipmentSlot::Armor) { + actions.push(Action::ArmorChange { + old_armor: Some(src_obj), + new_armor: None, + }); } - { + for action in actions { let owner = &mut world.objects().get_mut(self.owner); - if src_slot == Slot::Equipment(EquipmentSlot::Armor) { - let old_armor = world.objects().get(obj); - rpg.apply_armor_change(owner, None, Some(old_armor), world.objects()); - } else if target_slot == Slot::Equipment(EquipmentSlot::Armor) { - let new_armor = world.objects().get(obj); - let old_armor = existing.map(|obj| world.objects().get(obj)); - rpg.apply_armor_change(owner, Some(new_armor), old_armor, world.objects()); + match action { + Action::MoveTo { item, slot } => { + let mut item = world.objects().get_mut(item); + item.flags.remove(Flag::Worn | Flag::LeftHand | Flag::RightHand); + match slot { + Slot::Inventory => { + let i = owner.inventory.items.iter() + .position(|i| i.object == item.handle()).unwrap(); + if i > 0 { + let item = owner.inventory.items.remove(i); + owner.inventory.items.insert(0, item); + } + } + Slot::Equipment(eq_slot) => match eq_slot { + EquipmentSlot::Armor => item.flags.insert(Flag::Worn), + EquipmentSlot::Hand(Hand::Left) => item.flags.insert(Flag::LeftHand), + EquipmentSlot::Hand(Hand::Right) => item.flags.insert(Flag::RightHand), + } + } + } + Action::Reload { weapon, max_count } => { + if max_count <= 1 { + let mut weapon = world.objects().get_mut(weapon); + let mut ammo = world.objects().get_mut(src_obj); + weapon.sub.as_item_mut().unwrap().ammo_count += max_count; + let ammo = ammo.sub.as_item_mut().unwrap(); + ammo.ammo_count = ammo.ammo_count.checked_sub(max_count).unwrap(); + } else { + let win = InventoryMoveWindow::show( + weapon, + &world.objects().get(src_obj), + max_count, + &self.msgs, + ui); + assert!(self.move_window.replace(win).is_none()); + } + } + Action::ArmorChange { old_armor, new_armor } => { + let old_armor = old_armor.map(|obj| world.objects().get(obj)); + let new_armor = new_armor.map(|obj| world.objects().get(obj)); + rpg.apply_armor_change(owner, old_armor, new_armor, world.objects()); + } } } - self.sync_from_obj(rpg, ui); + self.sync_to_ui(rpg, ui); self.sync_owner_fid(rpg) } @@ -673,7 +715,49 @@ impl Internal { let ammo = unwrap_or_return!(world.objects_mut().unload_weapon(weapon), Some); world.objects_mut().move_into_inventory(self.owner, ammo, 1); } - self.sync_from_obj(rpg, ui); + self.sync_to_ui(rpg, ui); + } + + fn handle(&mut self, cmd: UiCommand, rpg: &Rpg, ui: &mut Ui) { + match cmd.data { + UiCommandData::Inventory(c) => match c { + Command::Show | Command::Hide => {} + Command::Scroll(scroll) => { + self.scroll(scroll, ui); + } + Command::Hover { .. } => {} + Command::ActionMenu { object } => { + self.show_action_menu(object, ui); + } + Command::Action { object, action } => { + self.hide_action_menu(ui); + if let Some(Action::Unload) = action { + self.unload(object, rpg, ui); + } + } + Command::ListDrop { pos, object } => { + self.handle_list_drop(cmd.source, pos, object, rpg, ui); + } + Command::ToggleMouseMode => { + self.toggle_mouse_mode(rpg, ui); + } + } + UiCommandData::MoveWindow(c) => { + if let move_window::Command::Hide { ok } = c { + let win = self.move_window.take().unwrap(); + if ok { + self.world.borrow_mut().objects_mut() + .reload_weapon_from_inventory(self.owner, win.weapon, win.ammo); + self.sync_to_ui(rpg, ui); + } + win.win.hide(ui); + } + } + _ => {} + } + if let Some(v) = self.move_window.as_mut() { + v.win.handle(cmd, ui); + } } } @@ -685,4 +769,28 @@ impl Widget for MouseModeToggler { ctx.out(UiCommandData::Inventory(Command::ToggleMouseMode)); } } +} + +struct InventoryMoveWindow { + weapon: object::Handle, + ammo: object::Handle, + win: MoveWindow, +} + +impl InventoryMoveWindow { + pub fn show( + weapon: object::Handle, + ammo: &Object, + max: u32, + msgs: &Messages, + ui: &mut Ui, + ) -> Self { + let fid = ammo.proto().unwrap().sub.as_item().unwrap().inventory_fid.unwrap(); + let win = MoveWindow::show(fid, max, msgs, ui); + Self { + weapon, + ammo: ammo.handle(), + win, + } + } } \ No newline at end of file diff --git a/src/game/object.rs b/src/game/object.rs index 0ef6157..5352ce2 100644 --- a/src/game/object.rs +++ b/src/game/object.rs @@ -529,6 +529,50 @@ impl Object { } } + // item_w_can_reload + #[must_use] + pub fn can_reload_weapon(&self, ammo: &Self) -> Option { + let weapon_proto = self.proto()?; + if weapon_proto.id() == ProtoId::SOLAR_SCORCHER { + unimplemented!("TODO"); + } + let weapon = self.sub.as_item()?; + let ammo_proto = ammo.proto()?; + let weapon_proto = weapon_proto.sub.as_weapon()?; + if weapon_proto.caliber == ammo_proto.sub.as_ammo()?.caliber + // TODO this is a weird condition + && (weapon.ammo_count == 0 || weapon.ammo_proto.as_ref()?.borrow().id() == ammo_proto.id()) + { + let free = weapon_proto.max_ammo_count.checked_sub(weapon.ammo_count).unwrap(); + let r = std::cmp::min(free, ammo.total_ammo_count(1).unwrap()); + Some(r) + } else { + None + } + } + + // item_w_reload + #[must_use] + pub fn reload_weapon(&mut self, ammo: &mut Object) -> Option { + let ammo_count = self.can_reload_weapon(ammo)?; + self.sub.as_item_mut()?.ammo_count += ammo_count; + let weapon_proto = self.proto()?; + if weapon_proto.id() == ProtoId::SOLAR_SCORCHER { + return Some(0); + } + + let ammo = ammo.sub.as_item_mut().unwrap(); + ammo.ammo_count -= ammo_count; + Some(ammo.ammo_count) + } + + #[must_use] + pub fn total_ammo_count(&self, clip_count: u32) -> Option { + let ammo_proto = self.proto()?; + Some(self.sub.as_item()?.ammo_count + + ammo_proto.sub.as_ammo()?.max_ammo_count * clip_count.saturating_sub(1)) + } + // item_w_curr_ammo #[must_use] pub fn ammo_count(&self) -> Option { @@ -968,6 +1012,14 @@ impl Objects { if obj.sub.as_critter().is_some() { rpg.unwrap().recalc_derived_stats(&mut obj, self); } + + if obj.is_dude() { + obj.sub.as_critter_mut().unwrap().dude = Some(Box::new(Dude { + naked_fidx: 0x3e, + active_hand: Hand::Left, + })); + } + // Not initializing script here. obj @@ -1617,6 +1669,31 @@ impl Objects { Some(ammo.handle()) } + pub fn reload_weapon_from_inventory(&mut self, + owner: Handle, + weapon: Handle, + ammo: Handle, + ) { + let remove = { + let owner = &mut self.get_mut(owner); + let weapon = &mut self.get_mut(weapon); + let ammo = &mut self.get_mut(ammo); + let idx = owner.inventory.items.iter() + .position(|i| i.object == ammo.handle()) + .unwrap(); + let left = unwrap_or_return!(weapon.reload_weapon(ammo), Some); + if left == 0 && owner.inventory.items[idx].count - 1 == 0 { + owner.inventory.items.remove(idx); + true + } else { + false + } + }; + if remove { + self.remove(ammo); + } + } + // obj_intersects_with() #[must_use] fn is_egg_hit(&self, p: Point, obj: &Object, egg: Egg, tile_grid: &impl TileGridView) -> bool { @@ -1932,6 +2009,7 @@ pub struct Elevator { #[derive(Debug)] pub struct Item { pub ammo_count: u32, + // TODO When can this be different from Weapon::ammo_proto_id? pub ammo_proto: Option, } diff --git a/src/game/skilldex.rs b/src/game/skilldex.rs index c7a8b48..8690cdc 100644 --- a/src/game/skilldex.rs +++ b/src/game/skilldex.rs @@ -115,7 +115,7 @@ impl Skilldex { ui.new_widget(window, Rect::with_points(pos, pos + btn_size), None, None, btn); let level: u32 = levels[skill].try_into().unwrap_or(0); - let mut level_wid = ImageText::standard_digits(FrameId::BIG_NUMBERS, 14); + let mut level_wid = ImageText::big_numbers(); *level_wid.text_mut() = format!("{:03}", level).into(); let pos = pos + Point::new(96, 3); ui.new_widget(window, Rect::with_size(pos.x, pos.y, 1, 1), None, None, level_wid); diff --git a/src/game/state.rs b/src/game/state.rs index a4b773d..0d91e86 100644 --- a/src/game/state.rs +++ b/src/game/state.rs @@ -1209,7 +1209,7 @@ impl AppState for GameState { } fn handle_ui_command(&mut self, command: UiCommand, ui: &mut Ui) { - self.inventory.handle(command.clone(), &self.rpg, ui); + self.inventory.handle(command, &self.rpg, ui); match command.data { UiCommandData::ObjectPick { kind, obj: objh } => { @@ -1386,6 +1386,7 @@ impl AppState for GameState { } _ => {} } + UiCommandData::MoveWindow(_) => {} } } diff --git a/src/game/ui.rs b/src/game/ui.rs index 68202c5..24ea0c8 100644 --- a/src/game/ui.rs +++ b/src/game/ui.rs @@ -1,5 +1,6 @@ pub mod action_menu; pub mod hud; pub mod inventory_list; +pub mod move_window; pub mod scroll_area; pub mod world; diff --git a/src/game/ui/action_menu.rs b/src/game/ui/action_menu.rs index 845246b..e664ab2 100644 --- a/src/game/ui/action_menu.rs +++ b/src/game/ui/action_menu.rs @@ -139,7 +139,7 @@ impl Widget for ActionMenu { }, Event::MouseUp { pos, .. } => { self.update_selection(ctx.base, pos); - ctx.out(self.actions[self.selection as usize].1.clone()); + ctx.out(self.actions[self.selection as usize].1); } _ => {} } diff --git a/src/game/ui/move_window.rs b/src/game/ui/move_window.rs new file mode 100644 index 0000000..5cabdb8 --- /dev/null +++ b/src/game/ui/move_window.rs @@ -0,0 +1,114 @@ +use crate::ui::{self, Ui}; +use crate::graphics::sprite::{Sprite, Effect}; +use crate::asset::frame::FrameId; +use crate::graphics::Rect; +use crate::ui::image_text::ImageText; +use crate::asset::message::Messages; +use crate::ui::panel::{self, Panel}; +use crate::graphics::font::{FontKey, DrawOptions, HorzAlign, VertAlign}; +use crate::graphics::color::Rgb15; +use crate::ui::button::{Button, Text}; +use bstring::bfmt::ToBString; +use crate::ui::command::move_window::Command; +use crate::ui::command::{UiCommand, UiCommandData}; + +pub struct MoveWindow { + max: u32, + win: ui::Handle, + count: ui::Handle, + value: u32, +} + +impl MoveWindow { + pub fn show(item_fid: FrameId, max: u32, msgs: &Messages, ui: &mut Ui) -> Self { + assert!(max > 0); + + let win = ui.new_window(Rect::with_size(140, 80, 259, 162), + Some(Sprite::new(FrameId::INVENTORY_MOVE_MULTIPLE_WINDOW))); + ui.widget_base_mut(win).set_modal(true); + + let mut header = Panel::new(); + header.set_text(Some(panel::Text { + text: msgs.get(21).unwrap().text.clone(), + font: FontKey::antialiased(3), + color: Rgb15::from_packed(0x5263), + options: DrawOptions { + horz_align: HorzAlign::Center, + ..Default::default() + }, + })); + ui.new_widget(win, Rect::with_size(0, 9, 259, 162), None, None, header); + + let mut item = Sprite::new(item_fid); + item.effect = Some(Effect::Fit { + width: 90, + height: 61, + }); + ui.new_widget(win, Rect::with_size(16, 46, 1, 1), None, Some(item), Panel::new()); + + let count = ui.new_widget(win, Rect::with_size(125, 45, 1, 1), None, None, + ImageText::big_numbers()); + + ui.new_widget(win, Rect::with_size(200, 46, 16, 12), None, None, + Button::new(FrameId::BUTTON_PLUS_UP, FrameId::BUTTON_PLUS_DOWN, + Some(UiCommandData::MoveWindow(Command::Inc)))); + ui.new_widget(win, Rect::with_size(200, 46 + 12, 16, 12), None, None, + Button::new(FrameId::BUTTON_MINUS_UP, FrameId::BUTTON_MINUS_DOWN, + Some(UiCommandData::MoveWindow(Command::Dec)))); + + ui.new_widget(win, Rect::with_size(98, 128, 15, 16), None, None, + Button::new(FrameId::SMALL_RED_BUTTON_UP, FrameId::SMALL_RED_BUTTON_DOWN, + Some(UiCommandData::MoveWindow(Command::Hide { ok: true })))); + ui.new_widget(win, Rect::with_size(148, 128, 15, 16), None, None, + Button::new(FrameId::SMALL_RED_BUTTON_UP, FrameId::SMALL_RED_BUTTON_DOWN, + Some(UiCommandData::MoveWindow(Command::Hide { ok: false })))); + + let mut text = Text::new(msgs.get(22).unwrap().text.clone(), FontKey::antialiased(3)); + text.color = Rgb15::from_packed(0x5263); + text.options.horz_align = HorzAlign::Center; + text.options.vert_align = VertAlign::Middle; + let mut all = Button::new(FrameId::BUTTON_ALL_UP, FrameId::BUTTON_ALL_DOWN, + Some(UiCommandData::MoveWindow(Command::Max))); + all.set_text(Some(text)); + ui.new_widget(win, Rect::with_size(121, 80, 94, 33), None, None, all); + + let r = Self { + max: std::cmp::min(max, 99999), + win, + count, + value: 1, + }; + r.sync(ui); + r + } + + pub fn hide(self, ui: &mut Ui) { + ui.remove(self.win); + } + + pub fn value(&self) -> u32 { + self.value + } + + pub fn handle(&mut self, cmd: UiCommand, ui: &Ui) { + if let UiCommandData::MoveWindow(cmd) = cmd.data { + let new_value = match cmd { + Command::Hide { .. } => { + return; + } + Command::Inc => std::cmp::min(self.value + 1, self.max), + Command::Dec => std::cmp::max(self.value - 1, 1), + Command::Max => self.max, + }; + if new_value != self.value { + self.value = new_value; + self.sync(ui); + } + } + } + + fn sync(&self, ui: &Ui) { + *ui.widget_mut::(self.count).text_mut() = + format!("{:05}", self.value).to_bstring(); + } +} \ No newline at end of file diff --git a/src/ui/button.rs b/src/ui/button.rs index 4e25afd..d7fddeb 100644 --- a/src/ui/button.rs +++ b/src/ui/button.rs @@ -109,7 +109,7 @@ impl Widget for Button { self.state = State::Up; // FIXME should optionally hit test the frame as in original. if ctx.base.rect.contains(pos) { - if let Some(cmd) = self.command.clone() { + if let Some(cmd) = self.command { ctx.out(cmd); } } diff --git a/src/ui/command.rs b/src/ui/command.rs index 02cb016..276c58c 100644 --- a/src/ui/command.rs +++ b/src/ui/command.rs @@ -4,13 +4,13 @@ use crate::game::object; use crate::graphics::EPoint; /// Command for signaling widget-specific events to callee. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct UiCommand { pub source: Handle, pub data: UiCommandData, } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum UiCommandData { ObjectPick { kind: ObjectPickKind, @@ -29,6 +29,7 @@ pub enum UiCommandData { Scroll, Skilldex(SkilldexCommand), Inventory(inventory::Command), + MoveWindow(move_window::Command), } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -77,3 +78,14 @@ pub mod inventory { } } +pub mod move_window { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub enum Command { + Hide { + ok: bool + }, + Inc, + Dec, + Max, + } +} \ No newline at end of file diff --git a/src/ui/image_text.rs b/src/ui/image_text.rs index c4752aa..3c8f5fb 100644 --- a/src/ui/image_text.rs +++ b/src/ui/image_text.rs @@ -26,6 +26,10 @@ impl ImageText { } } + pub fn big_numbers() -> Self { + Self::standard_digits(FrameId::BIG_NUMBERS, 14) + } + pub fn text(&self) -> &BString { &self.text }