pvz_rust_wsll_lib/ui/input_handler.rs
1//! # 输入处理模块 (`input_handler`)
2//!
3//! 负责处理游戏中的用户输入,主要是鼠标点击事件。
4//! 它管理着阳光的收集、商店中植物卡片的选择、以及在网格上放置植物的逻辑。
5
6use crate::ui::grid::{Grid, GRID_START_X, GRID_START_Y, GRID_CELL_HEIGHT, GRID_CELL_WIDTH, GRID_WIDTH, GRID_HEIGHT};
7use crate::plants::{Plant, PlantType};
8use crate::ui::shop::{Shop, SHOP_START_Y, CARD_HEIGHT};
9use crate::ui::shovel::Shovel;
10use crate::entities::sun::Sun;
11use ggez::input::mouse::MouseButton;
12
13/// `InputHandler` 结构体。
14///
15/// 这是一个单元结构体,目前仅包含静态方法用于处理输入事件。
16/// 它不持有任何状态,而是直接操作传入的游戏状态变量。
17pub struct InputHandler;
18
19impl InputHandler {
20 /// 处理鼠标按下事件。
21 ///
22 /// 根据鼠标点击的位置和按键,执行相应的游戏操作,例如:
23 /// - 点击阳光:收集阳光并增加阳光计数。
24 /// - 点击商店卡片:如果阳光充足,则选择相应的植物类型以备放置。
25 /// - 在网格上点击(已选择植物时):如果单元格未被占据且阳光充足,则放置植物。
26 /// - 右键点击:取消当前选择的植物。
27 ///
28 /// # Arguments
29 ///
30 /// * `button` - 按下的鼠标按键 (`ggez::input::mouse::MouseButton`)。
31 /// * `x` - 鼠标点击的屏幕X坐标。
32 /// * `y` - 鼠标点击的屏幕Y坐标。
33 /// * `shop` - 可变的 `Shop` 引用,用于处理商店交互和更新选择状态。
34 /// * `suns` - 可变的阳光实体列表 (`Vec<Sun>`),用于检查点击和移除被收集的阳光。
35 /// * `grid` - 可变的 `Grid` 引用,用于检查网格位置和标记占据状态。
36 /// * `plants` - 可变的植物实体列表 (`Vec<Plant>`),用于添加新放置的植物。
37 /// * `selected_plant` - 可变的 `Option<PlantType>`,表示当前从商店选择的植物类型。
38 /// * `sun_count` - 可变的阳光计数器 (`i32`)。
39 /// * `game_over` - 一个布尔值,指示游戏是否已结束。如果为 `true`,则不处理输入。
40 ///
41 /// # Returns
42 ///
43 /// 如果输入事件导致了游戏状态的改变(例如收集了阳光、选择了植物、放置了植物),
44 /// 则返回 `true`,否则返回 `false`。
45 pub fn handle_mouse_down(
46 button: MouseButton,
47 x: f32,
48 y: f32,
49 shop: &mut Shop,
50 suns: &mut Vec<Sun>,
51 grid: &mut Grid,
52 plants: &mut Vec<Plant>,
53 selected_plant: &mut Option<PlantType>,
54 sun_count: &mut i32,
55 game_over: bool,
56 shovel: &mut Shovel
57 ) -> bool {
58 if game_over {
59 return false;
60 }
61
62 if button == MouseButton::Left {
63 // 检查是否点击了铲子
64 if shovel.is_clicked(x, y) {
65 shovel.is_dragging = true;
66 // 取消选中植物
67 *selected_plant = None;
68 shop.selected_plant = None;
69 return true;
70 }
71
72 // 如果铲子正在拖动中,检查是否可以铲除植物
73 if shovel.is_dragging {
74 if shovel.dig(x, y, grid, plants) {
75 // 铲除成功,重置铲子状态
76 shovel.reset();
77 return true;
78 }
79 return false; // 点击在网格外或没有植物,铲子动作不生效
80 }
81
82 // 检查是否点击了阳光
83 let initial_sun_count = *sun_count;
84 suns.retain(|sun| {
85 if sun.contains_point(x, y) {
86 *sun_count += 25;
87 false // Remove the sun
88 } else {
89 true // Keep the sun
90 }
91 });
92 if *sun_count > initial_sun_count {
93 return true; // 如果点击了阳光,不处理其他点击
94 }
95
96 // 处理商店卡片点击 (优先于放置植物)
97 if y < SHOP_START_Y + CARD_HEIGHT + 20.0 { // 商店区域的大致检查
98 if let Some(plant_type) = shop.handle_click(x, y, *sun_count) {
99 *selected_plant = Some(plant_type);
100 return true; // 如果点击了卡片,停止处理
101 }
102 }
103
104 // 处理植物放置逻辑
105 if selected_plant.is_some() {
106 if x >= GRID_START_X && x <= GRID_START_X + GRID_CELL_WIDTH * GRID_WIDTH as f32 &&
107 y >= GRID_START_Y && y <= GRID_START_Y + GRID_CELL_HEIGHT * GRID_HEIGHT as f32 {
108 if Self::place_plant(x, y, grid, plants, selected_plant, sun_count, shop) {
109 // 放置成功,清除选择状态
110 shop.selected_plant = None;
111 return true;
112 } else {
113 // 如果放置失败,取消选择
114 *selected_plant = None;
115 shop.selected_plant = None;
116 return false;
117 }
118 } else {
119 // 点击在网格外,取消选择
120 *selected_plant = None;
121 shop.selected_plant = None;
122 return false;
123 }
124 }
125 } else if button == MouseButton::Right {
126 // 右键取消选择
127 *selected_plant = None;
128 shop.selected_plant = None;
129 return true;
130 }
131
132 false
133 }
134
135 /// 处理在网格上放置植物的逻辑。
136 ///
137 /// 当玩家在选择了植物类型后点击网格时,此方法被调用。
138 /// 它会检查目标网格单元格是否有效、是否已被占据,以及玩家是否有足够的阳光。
139 /// 如果所有条件满足,则创建一个新的植物实例,将其添加到游戏世界,
140 /// 更新阳光数量,标记网格单元格为已占据,并清除当前的植物选择状态。
141 ///
142 /// # Arguments
143 ///
144 /// * `x` - 鼠标点击的屏幕X坐标。
145 /// * `y` - 鼠标点击的屏幕Y坐标。
146 /// * `grid` - 可变的 `Grid` 引用。
147 /// * `plants` - 可变的植物实体列表 (`Vec<Plant>`)。
148 /// * `selected_plant` - 可变的 `Option<PlantType>`,包含待放置的植物类型。
149 /// * `sun_count` - 可变的阳光计数器 (`i32`)。
150 ///
151 /// # Returns
152 ///
153 /// 如果植物成功放置,则返回 `true`。否则(例如,位置无效、已被占据、阳光不足),
154 /// 返回 `false`。
155 fn place_plant(
156 x: f32,
157 y: f32,
158 grid: &mut Grid,
159 plants: &mut Vec<Plant>,
160 selected_plant: &mut Option<PlantType>,
161 sun_count: &mut i32,
162 shop: &mut Shop
163 ) -> bool {
164 if let Some(plant_type) = selected_plant {
165 if let Some((grid_x, grid_y)) = grid.get_grid_position(x, y) {
166 // 检查是否已有植物
167 if !grid.is_occupied(grid_x, grid_y) && *sun_count >= plant_type.cost() {
168 let plant = Plant::new(*plant_type, grid_x, grid_y);
169 plants.push(plant);
170 *sun_count -= plant_type.cost();
171 grid.occupy(grid_x, grid_y);
172
173 // 在植物成功放置后,才触发卡片冷却
174 shop.trigger_card_cooldown(*plant_type);
175
176 // 放置植物后取消选择状态
177 *selected_plant = None;
178
179 return true;
180 }
181 }
182 }
183 false
184 }
185}