Skip to main content

rustapi/tools/
game.rs

1use crate::{editor::RUSTERIX, prelude::*};
2use MapEvent::*;
3use rusterix::{EntityAction, PlayerCamera, Value};
4use std::sync::Mutex;
5use theframework::prelude::*;
6
7pub struct GameTool {
8    id: TheId,
9
10    right: Option<Mutex<Box<TheCanvas>>>,
11    toolbar: Option<Mutex<Box<TheCanvas>>>,
12    editor_routed_prev_camera: Option<PlayerCamera>,
13}
14
15impl Tool for GameTool {
16    fn new() -> Self
17    where
18        Self: Sized,
19    {
20        Self {
21            id: TheId::named("Game Tool"),
22
23            right: None,
24            toolbar: None,
25            editor_routed_prev_camera: None,
26        }
27    }
28
29    fn id(&self) -> TheId {
30        self.id.clone()
31    }
32    fn info(&self) -> String {
33        fl!("tool_game")
34    }
35    fn icon_name(&self) -> String {
36        str!("joystick")
37    }
38    fn accel(&self) -> Option<char> {
39        Some('G')
40    }
41
42    fn help_url(&self) -> Option<String> {
43        Some("docs/creator/tools/overview".to_string())
44    }
45
46    fn tool_event(
47        &mut self,
48        tool_event: ToolEvent,
49        ui: &mut TheUI,
50        ctx: &mut TheContext,
51        project: &mut Project,
52        server_ctx: &mut ServerContext,
53    ) -> bool {
54        match tool_event {
55            ToolEvent::Activate => {
56                self.toolbar = None;
57                if let Some(layout) = ui.get_sharedvlayout("Shared VLayout") {
58                    layout.set_mode(TheSharedVLayoutMode::Top);
59                    if let Some(canvas) = layout.get_canvas_mut(0) {
60                        if let Some(tool) = canvas.bottom.take() {
61                            self.toolbar = Some(Mutex::new(tool));
62                        }
63                    }
64                }
65                server_ctx.curr_map_tool_type = MapToolType::Game;
66                server_ctx.game_mode = true;
67
68                // If editor-routed input temporarily forced a different camera mapping
69                // (e.g. Iso), restore the previous game camera mapping when entering
70                // actual Game Tool mode.
71                if RUSTERIX.read().unwrap().server.state == rusterix::ServerState::Running {
72                    let map_camera_to_player_camera = |mode: MapCamera| -> PlayerCamera {
73                        match mode {
74                            MapCamera::TwoD => PlayerCamera::D2,
75                            MapCamera::ThreeDIso => PlayerCamera::D3Iso,
76                            MapCamera::ThreeDFirstPerson => PlayerCamera::D3FirstP,
77                        }
78                    };
79                    let restore_camera = if let Some(prev) = self.editor_routed_prev_camera.take() {
80                        prev
81                    } else {
82                        let rusterix = RUSTERIX.read().unwrap();
83                        let by_active_game_widget =
84                            rusterix.client.active_game_widget_camera_mode();
85                        let by_running_map = project
86                            .regions
87                            .iter()
88                            .find(|r| r.map.name == rusterix.client.current_map)
89                            .map(|r| map_camera_to_player_camera(r.map.camera));
90                        let by_editor_region = project
91                            .get_region(&server_ctx.curr_region)
92                            .map(|r| map_camera_to_player_camera(r.map.camera));
93                        by_active_game_widget
94                            .or(by_running_map)
95                            .or(by_editor_region)
96                            .unwrap_or_else(|| rusterix.player_camera.clone())
97                    };
98
99                    RUSTERIX
100                        .write()
101                        .unwrap()
102                        .server
103                        .local_player_action(EntityAction::SetPlayerCamera(restore_camera));
104                } else {
105                    self.editor_routed_prev_camera = None;
106                }
107
108                if let Some(right) = ui.canvas.right.take() {
109                    self.right = Some(Mutex::new(right));
110                }
111                ctx.ui.redraw_all = true;
112                ctx.ui.relayout = true;
113
114                true
115            }
116            ToolEvent::DeActivate => {
117                ctx.set_cursor_visible(true);
118
119                if let Some(layout) = ui.get_sharedvlayout("Shared VLayout") {
120                    layout.set_mode(TheSharedVLayoutMode::Shared);
121                    if let Some(canvas) = layout.get_canvas_mut(0) {
122                        if let Some(tool) = &mut self.toolbar {
123                            let lock = tool.get_mut().unwrap();
124                            let boxed_canvas: Box<TheCanvas> =
125                                std::mem::replace(&mut *lock, Box::new(TheCanvas::default()));
126                            canvas.bottom = Some(boxed_canvas);
127                        }
128                    }
129                }
130
131                if let Some(right) = &mut self.right {
132                    let lock = right.get_mut().unwrap();
133                    let boxed_canvas: Box<TheCanvas> =
134                        std::mem::replace(&mut *lock, Box::new(TheCanvas::default()));
135                    ui.canvas.right = Some(boxed_canvas);
136                    ctx.ui.redraw_all = true;
137                    ctx.ui.relayout = true;
138                }
139
140                server_ctx.game_mode = false;
141                true
142            }
143            _ => false,
144        }
145    }
146
147    fn map_event(
148        &mut self,
149        map_event: MapEvent,
150        _ui: &mut TheUI,
151        ctx: &mut TheContext,
152        map: &mut Map,
153        _server_ctx: &mut ServerContext,
154    ) -> Option<ProjectUndoAtom> {
155        match map_event {
156            MapClicked(coord) => {
157                let mut rusterix = RUSTERIX.write().unwrap();
158                let is_running = rusterix.server.state == rusterix::ServerState::Running;
159
160                if is_running {
161                    if let Some(action) = rusterix.client.touch_down(coord, map) {
162                        rusterix.server.local_player_action(action);
163                    }
164                }
165            }
166            MapDragged(coord) => {
167                let mut rusterix = RUSTERIX.write().unwrap();
168                let is_running = rusterix.server.state == rusterix::ServerState::Running;
169
170                let is_inside = rusterix.client.is_inside_game(coord);
171                if is_running && is_inside {
172                    ctx.set_cursor_visible(false);
173                    rusterix.client_touch_dragged(coord, map);
174                } else {
175                    ctx.set_cursor_visible(true);
176                }
177            }
178            MapUp(coord) => {
179                let mut rusterix = RUSTERIX.write().unwrap();
180                let is_running = rusterix.server.state == rusterix::ServerState::Running;
181
182                if is_running {
183                    if let Some(action) = rusterix.client.touch_up(coord, map) {
184                        rusterix.server.local_player_action(action);
185                    }
186                    rusterix.server.local_player_action(EntityAction::Off);
187                }
188            }
189            _ => {}
190        }
191
192        None
193    }
194
195    fn handle_event(
196        &mut self,
197        event: &TheEvent,
198        _ui: &mut TheUI,
199        ctx: &mut TheContext,
200        project: &mut Project,
201        server_ctx: &mut ServerContext,
202    ) -> bool {
203        let editor_view_to_player_camera = |mode: EditorViewMode| match mode {
204            EditorViewMode::D2 => PlayerCamera::D2,
205            EditorViewMode::FirstP => PlayerCamera::D3FirstP,
206            EditorViewMode::Iso | EditorViewMode::Orbit => PlayerCamera::D3Iso,
207        };
208
209        #[allow(clippy::single_match)]
210        match event {
211            TheEvent::KeyDown(TheValue::Char(char)) => {
212                let mut rusterix = crate::editor::RUSTERIX.write().unwrap();
213                if rusterix.server.state == rusterix::ServerState::Running {
214                    if server_ctx.game_input_mode && !server_ctx.game_mode {
215                        // While routing editor input, temporarily enforce movement mapping from
216                        // the current editor camera mode (D2 / Iso / FirstP).
217                        if self.editor_routed_prev_camera.is_none() {
218                            self.editor_routed_prev_camera = Some(rusterix.player_camera.clone());
219                        }
220                        let camera = editor_view_to_player_camera(server_ctx.editor_view_mode);
221                        rusterix
222                            .server
223                            .local_player_action(EntityAction::SetPlayerCamera(camera));
224
225                        let action = rusterix
226                            .client
227                            .user_event("key_down".into(), Value::Str(char.to_string()));
228                        rusterix.server.local_player_action(action);
229                    } else {
230                        let action = rusterix
231                            .client
232                            .user_event("key_down".into(), Value::Str(char.to_string()));
233
234                        rusterix.server.local_player_action(action);
235                    }
236                }
237            }
238            TheEvent::KeyUp(TheValue::Char(char)) => {
239                let mut rusterix = crate::editor::RUSTERIX.write().unwrap();
240                if rusterix.server.state == rusterix::ServerState::Running {
241                    if server_ctx.game_input_mode && !server_ctx.game_mode {
242                        let action = rusterix
243                            .client
244                            .user_event("key_up".into(), Value::Str(char.to_string()));
245                        rusterix.server.local_player_action(action);
246                        if let Some(prev) = self.editor_routed_prev_camera.take() {
247                            rusterix
248                                .server
249                                .local_player_action(EntityAction::SetPlayerCamera(prev));
250                        }
251                    } else {
252                        let action = rusterix
253                            .client
254                            .user_event("key_up".into(), Value::Str(char.to_string()));
255                        rusterix.server.local_player_action(action);
256                    }
257                }
258            }
259            TheEvent::RenderViewHoverChanged(id, coord) => {
260                // Do not run "real game play" hover/cursor logic when only routing input
261                // from the editor view into the running server.
262                if server_ctx.game_input_mode && !server_ctx.game_mode {
263                    return false;
264                }
265                if id.name == "PolyView" {
266                    let mut rusterix = RUSTERIX.write().unwrap();
267                    let is_running = rusterix.server.state == rusterix::ServerState::Running;
268
269                    let is_inside = rusterix.client.is_inside_game(*coord);
270                    if is_running && is_inside {
271                        ctx.set_cursor_visible(false);
272
273                        for region in &project.regions {
274                            if region.map.name == rusterix.client.current_map {
275                                rusterix.client_touch_hover(*coord, &region.map);
276                                break;
277                            }
278                        }
279                    } else {
280                        ctx.set_cursor_visible(true);
281                    }
282                }
283            }
284            _ => {}
285        }
286
287        false
288    }
289}