1pub mod command;
20
21use crate::generator::parser::command::{
22 argument::{
23 minecraft::{MinecraftEntityAnchor, MinecraftMessage, MinecraftScoreHolder, MinecraftTime},
24 Argument,
25 },
26 resource_location::{ResourceLocation, ResourceLocationRef},
27 CommandParser, CommandParserError, CommandParserResult, ParsedNode,
28};
29use log::debug;
30use std::{
31 collections::{BTreeMap, BTreeSet},
32 convert::TryFrom,
33 usize,
34};
35
36use self::command::argument::minecraft::{
37 entity::{MinecraftEntity, MinecraftSelector, MinecraftSelectorType},
38 MinecraftFunction, MinecraftObjective,
39};
40
41#[derive(Clone, Debug, PartialEq)]
42pub enum Line {
43 Empty,
44 Comment,
45 FunctionCall {
46 column_index: usize,
47 name: ResourceLocation,
48 anchor: Option<MinecraftEntityAnchor>,
49 selectors: BTreeMap<usize, SelectorValue>,
50 objectives: BTreeSet<String>,
51 },
52 OptionalSelectorCommand {
53 missing_selector: usize,
54 selectors: BTreeMap<usize, SelectorValue>,
55 objectives: BTreeSet<String>,
56 },
57 Schedule {
58 schedule_start: usize,
59 function: ResourceLocation,
60 operation: ScheduleOperation,
61 selectors: BTreeMap<usize, SelectorValue>,
62 objectives: BTreeSet<String>,
63 },
64 OtherCommand {
65 selectors: BTreeMap<usize, SelectorValue>,
66 objectives: BTreeSet<String>,
67 },
68}
69
70impl Line {
71 pub fn objectives(&self) -> Option<&BTreeSet<String>> {
72 match self {
73 Line::FunctionCall { objectives, .. }
74 | Line::OptionalSelectorCommand { objectives, .. }
75 | Line::Schedule { objectives, .. }
76 | Line::OtherCommand { objectives, .. } => Some(objectives),
77 _ => None,
78 }
79 }
80}
81
82#[derive(Clone, Debug, PartialEq)]
83pub enum ScheduleOperation {
84 Append { time: MinecraftTime },
85 Clear,
86 Replace { time: MinecraftTime },
87}
88
89pub fn parse_line(parser: &CommandParser, line: &str) -> Line {
90 let (line, error) = parse_line_internal(parser, line);
91 if let Some(error) = error {
92 debug!("Failed to parse command: {}", error);
93 }
94 line
95}
96
97fn parse_line_internal<'l>(
98 parser: &'l CommandParser,
99 line: &'l str,
100) -> (Line, Option<CommandParserError<'l>>) {
101 let line = line.trim();
102 if line.starts_with('#') {
103 (Line::Comment, None)
104 } else if line.is_empty() {
105 (Line::Empty, None)
106 } else {
107 parse_command(parser, line)
108 }
109}
110
111fn parse_command<'l>(
112 parser: &'l CommandParser,
113 command: &'l str,
114) -> (Line, Option<CommandParserError<'l>>) {
115 let CommandParserResult {
116 parsed_nodes,
117 error,
118 } = parser.parse(command);
119 let mut nodes = parsed_nodes.as_slice();
120 let mut selectors = BTreeMap::new();
121 let mut objectives = BTreeSet::new();
122 let mut maybe_anchor: Option<MinecraftEntityAnchor> = None;
123
124 while let [_, tail @ ..] = nodes {
125 match nodes {
126 [ParsedNode::Argument {
127 argument: Argument::MinecraftEntity(entity),
128 index,
129 ..
130 }, ..] => {
131 selectors.insert(*index, SelectorValue::from(entity));
132 }
133 [ParsedNode::Argument {
134 argument: Argument::MinecraftScoreHolder(MinecraftScoreHolder::Selector(selector)),
135 index,
136 ..
137 }, ..] => {
138 selectors.insert(*index, SelectorValue::from(selector));
139 }
140
141 [ParsedNode::Argument {
142 argument:
143 Argument::MinecraftMessage(MinecraftMessage {
144 selectors: message_selectors,
145 ..
146 }),
147 index,
148 ..
149 }, ..] => {
150 selectors.extend(
151 message_selectors.iter().map(|(selector, start, _end)| {
152 (index + start, SelectorValue::from(selector))
153 }),
154 );
155 }
156
157 [ParsedNode::Argument {
158 argument: Argument::MinecraftObjective(MinecraftObjective(objective)),
159 ..
160 }, ..]
161 | [ParsedNode::Literal {
162 literal: "scoreboard",
163 ..
164 }, ParsedNode::Literal {
165 literal: "objectives",
166 ..
167 }, ParsedNode::Literal { literal: "add", .. }, ParsedNode::Argument {
168 argument: Argument::BrigadierString(objective),
169 ..
170 }, ..] => {
171 objectives.insert(objective.to_string());
172 }
173
174 [ParsedNode::Literal {
175 literal: "execute", ..
176 }
177 | ParsedNode::Redirect("execute"), ParsedNode::Literal {
178 literal: "anchored",
179 ..
180 }, ParsedNode::Argument {
181 argument: Argument::MinecraftEntityAnchor(anchor),
182 ..
183 }, ..] => {
184 maybe_anchor = Some(*anchor);
185 }
186
187 _ => {}
188 }
189
190 nodes = tail;
191 }
192
193 if error.is_none() {
194 if let Some((column_index, name)) = as_function_call(&parsed_nodes) {
195 return (
196 Line::FunctionCall {
197 column_index,
198 name,
199 anchor: maybe_anchor,
200 selectors,
201 objectives,
202 },
203 None,
204 );
205 }
206
207 if let Some((schedule_start, function, operation)) = as_schedule(&parsed_nodes) {
208 return (
209 Line::Schedule {
210 schedule_start,
211 function: function.to_owned(),
212 operation,
213 selectors,
214 objectives,
215 },
216 None,
217 );
218 }
219
220 if let Some(missing_selector) = find_missing_selector(&parsed_nodes) {
221 return (
222 Line::OptionalSelectorCommand {
223 missing_selector,
224 selectors,
225 objectives,
226 },
227 None,
228 );
229 }
230 }
231
232 (
233 Line::OtherCommand {
234 selectors,
235 objectives,
236 },
237 error,
238 )
239}
240
241fn as_function_call(nodes: &[ParsedNode]) -> Option<(usize, ResourceLocation)> {
242 if let [.., ParsedNode::Literal {
243 literal: "function",
244 index,
245 ..
246 }, ParsedNode::Argument {
247 argument: Argument::MinecraftFunction(MinecraftFunction(function)),
248 ..
249 }] = nodes
250 {
251 Some((*index, function.to_owned()))
252 } else {
253 None
254 }
255}
256
257fn as_schedule(mut nodes: &[ParsedNode]) -> Option<(usize, ResourceLocation, ScheduleOperation)> {
258 while let [_, tail @ ..] = nodes {
259 match nodes {
260 [ParsedNode::Literal {
261 literal: "schedule",
262 index,
263 ..
264 }, ParsedNode::Literal {
265 literal: "function",
266 ..
267 }, ParsedNode::Argument {
268 argument: Argument::MinecraftFunction(MinecraftFunction(function)),
269 ..
270 }, ParsedNode::Argument {
271 argument: Argument::MinecraftTime(time),
272 ..
273 }, tail @ ..] => {
274 let op = match tail {
275 [ParsedNode::Literal {
276 literal: "append", ..
277 }] => Some(ScheduleOperation::Append { time: time.clone() }),
278 []
279 | [ParsedNode::Literal {
280 literal: "replace", ..
281 }] => Some(ScheduleOperation::Replace { time: time.clone() }),
282 _ => None,
283 };
284 if let Some(op) = op {
285 return Some((*index, function.to_owned(), op));
286 }
287 }
288
289 [ParsedNode::Literal {
290 literal: "schedule",
291 index,
292 ..
293 }, ParsedNode::Literal {
294 literal: "clear", ..
295 }, ParsedNode::Argument {
296 argument: Argument::BrigadierString(string),
297 ..
298 }] => {
299 if let Ok(function) = ResourceLocationRef::try_from(*string) {
301 return Some((*index, function.to_owned(), ScheduleOperation::Clear));
302 }
303 }
304 _ => {}
305 }
306
307 nodes = tail;
308 }
309
310 None
311}
312
313fn find_missing_selector(tail: &[ParsedNode]) -> Option<usize> {
314 match tail {
315 [.., ParsedNode::Literal {
316 literal: kill @ "kill",
317 index,
318 }] => Some(index + kill.len()),
319
320 [.., ParsedNode::Literal {
321 literal: "team", ..
322 }, ParsedNode::Literal {
323 literal: "join", ..
324 }, ParsedNode::Argument {
325 argument: Argument::MinecraftTeam(..),
326 index,
327 len,
328 ..
329 }] => Some(index + len),
330
331 [.., ParsedNode::Redirect("teleport")
332 | ParsedNode::Literal {
333 literal: "teleport",
334 ..
335 }, ParsedNode::Argument {
336 name: "destination",
337 argument: Argument::MinecraftEntity(..),
338 index,
339 ..
340 }
341 | ParsedNode::Argument {
342 name: "location",
343 argument: Argument::MinecraftVec3(..),
344 index,
345 ..
346 }] => Some(index - 1),
347
348 _ => None,
349 }
350}
351
352#[derive(Clone, Debug, PartialEq)]
353pub struct SelectorValue {
354 pub exclude_minect_cursor: bool,
355}
356impl SelectorValue {
357 fn entity_exclude_minect_cursor(value: &MinecraftEntity<'_>) -> bool {
358 match value {
359 MinecraftEntity::Selector(selector) => Self::selector_exclude_minect_cursor(selector),
360 MinecraftEntity::PlayerNameOrUuid(..) => false,
361 }
362 }
363
364 fn selector_exclude_minect_cursor(value: &MinecraftSelector<'_>) -> bool {
365 value.selector_type != MinecraftSelectorType::S
366 && !value.tags.iter().any(|it| it.string == "minect_cursor")
367 }
368
369 pub(crate) fn empty_self() -> SelectorValue {
370 Self {
371 exclude_minect_cursor: false,
372 }
373 }
374}
375impl Default for SelectorValue {
376 fn default() -> Self {
377 Self {
378 exclude_minect_cursor: true,
379 }
380 }
381}
382impl From<&MinecraftSelector<'_>> for SelectorValue {
383 fn from(value: &MinecraftSelector<'_>) -> Self {
384 Self {
385 exclude_minect_cursor: Self::selector_exclude_minect_cursor(value),
386 }
387 }
388}
389impl From<&MinecraftEntity<'_>> for SelectorValue {
390 fn from(value: &MinecraftEntity<'_>) -> Self {
391 Self {
392 exclude_minect_cursor: Self::entity_exclude_minect_cursor(value),
393 }
394 }
395}
396
397#[cfg(test)]
398mod tests;