1use configs::game_config::GameConfig;
32use essences::character_state::CharacterState;
33use event_system::script::random::GameRng;
34use uuid::Uuid;
35
36use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
37use crate::event::OverlordEvent;
38use crate::mechanics::content_lookups::ContentLookups;
39use crate::mechanics::loop_tasks;
40
41pub struct AdditionalQuestsCtx<'a> {
45 pub character_state: &'a CharacterState,
47 pub rng: &'a GameRng,
49 pub config: &'a GameConfig,
50 pub lookups: &'a ContentLookups,
51}
52
53pub type AdditionalQuestsFn = fn(&AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>>;
55
56fn set_custom_value(key: &str, value: i64) -> OverlordEvent {
57 OverlordEvent::SetCustomValue {
58 key: key.to_string(),
59 value,
60 }
61}
62
63fn finish_and_advance(ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
69 let mut events = Vec::new();
70 loop_tasks::on_finish_regular_loop_task(&mut events, ctx.character_state);
71 loop_tasks::advance_loop(&mut events, ctx.config, ctx.lookups, ctx.character_state);
72 Ok(events)
73}
74
75fn set_value_then_advance(
77 ctx: &AdditionalQuestsCtx,
78 key: &str,
79 value: i64,
80) -> anyhow::Result<Vec<OverlordEvent>> {
81 let mut events = vec![set_custom_value(key, value)];
82 loop_tasks::advance_loop(&mut events, ctx.config, ctx.lookups, ctx.character_state);
83 Ok(events)
84}
85
86fn set_value_only(
88 _ctx: &AdditionalQuestsCtx,
89 key: &str,
90 value: i64,
91) -> anyhow::Result<Vec<OverlordEvent>> {
92 Ok(vec![set_custom_value(key, value)])
93}
94
95pub fn loop_finish_advance(ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
103 finish_and_advance(ctx)
104}
105
106pub fn loop_finish_advance_first_crystals(
108 ctx: &AdditionalQuestsCtx,
109) -> anyhow::Result<Vec<OverlordEvent>> {
110 let mut events = finish_and_advance(ctx)?;
111 events.push(set_custom_value(
112 "loop_tasks.first_loop_skill_crystals_received",
113 1,
114 ));
115 Ok(events)
116}
117
118pub fn loop_finish_prepare_advance(
121 ctx: &AdditionalQuestsCtx,
122) -> anyhow::Result<Vec<OverlordEvent>> {
123 let mut events = Vec::new();
124 loop_tasks::on_finish_regular_loop_task(&mut events, ctx.character_state);
125 loop_tasks::prepare_loop(&mut events, ctx.config, ctx.character_state, ctx.rng);
126 loop_tasks::advance_loop(&mut events, ctx.config, ctx.lookups, ctx.character_state);
127 Ok(events)
128}
129
130pub fn loop_prepare_advance(ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
132 let mut events = Vec::new();
133 loop_tasks::prepare_loop(&mut events, ctx.config, ctx.character_state, ctx.rng);
134 loop_tasks::advance_loop(&mut events, ctx.config, ctx.lookups, ctx.character_state);
135 Ok(events)
136}
137
138pub fn milestone_level<const N: i64>(
142 ctx: &AdditionalQuestsCtx,
143) -> anyhow::Result<Vec<OverlordEvent>> {
144 set_value_then_advance(ctx, "loop_tasks.last_milestone.level", N)
145}
146
147pub fn milestone_stage<const N: i64>(
149 ctx: &AdditionalQuestsCtx,
150) -> anyhow::Result<Vec<OverlordEvent>> {
151 set_value_then_advance(ctx, "loop_tasks.last_milestone.stage", N)
152}
153
154pub fn mimic_next_item<const CODE: i64>(
158 ctx: &AdditionalQuestsCtx,
159) -> anyhow::Result<Vec<OverlordEvent>> {
160 set_value_only(ctx, "next_mimic_item_code", CODE)
161}
162
163pub const REDIRECT_KILL_3_ENEMIES: u128 = 0x019cd267_a50f_7d1b_81a5_83147141fb8c;
168pub const REDIRECT_OPEN_CHEST_6: u128 = 0x019cd268_6b9e_7b89_826c_96b0e1cb4c55;
169pub const REDIRECT_SELL_5_GEAR: u128 = 0x019cd268_a523_7e35_bb43_3cff76868568;
170pub const REDIRECT_REACH_LEVEL_3: u128 = 0x019cd269_8571_7361_936b_08a48ca9cffe;
171
172pub fn redirect_to<const QUEST: u128>(
174 _ctx: &AdditionalQuestsCtx,
175) -> anyhow::Result<Vec<OverlordEvent>> {
176 Ok(vec![OverlordEvent::UpdateActiveLoopTaskId {
177 quest_id: Uuid::from_u128(QUEST),
178 }])
179}
180
181fn set_loop_task_and_redirect(value: i64, next_quest_id: &str) -> Vec<OverlordEvent> {
193 let quest_id = Uuid::parse_str(next_quest_id).expect("valid quest uuid literal");
194 vec![
195 set_custom_value("loop_task", value),
196 OverlordEvent::UpdateActiveLoopTaskId { quest_id },
197 ]
198}
199
200pub fn test_loop_task_1(_ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
202 Ok(set_loop_task_and_redirect(
203 1,
204 "29540ca2-21f3-4f0e-a478-240adafed4e3",
205 ))
206}
207
208pub fn test_loop_task_2(_ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
210 Ok(set_loop_task_and_redirect(
211 2,
212 "9d3d2428-af3c-409f-a580-593de0fd06cf",
213 ))
214}
215
216pub fn test_loop_task_0(_ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
218 Ok(set_loop_task_and_redirect(
219 0,
220 "305300e2-f432-4cbf-ad46-85386a9a9410",
221 ))
222}
223
224pub fn test_loop_task_seed(_ctx: &AdditionalQuestsCtx) -> anyhow::Result<Vec<OverlordEvent>> {
231 Ok(vec![
232 set_custom_value("loop_task", 0),
233 OverlordEvent::NewQuests {
234 quest_ids: vec![
235 Uuid::parse_str("305300e2-f432-4cbf-ad46-85386a9a9410").unwrap(),
236 Uuid::parse_str("29540ca2-21f3-4f0e-a478-240adafed4e3").unwrap(),
237 Uuid::parse_str("9d3d2428-af3c-409f-a580-593de0fd06cf").unwrap(),
238 ],
239 },
240 OverlordEvent::UpdateActiveLoopTaskId {
241 quest_id: Uuid::parse_str("305300e2-f432-4cbf-ad46-85386a9a9410").unwrap(),
242 },
243 ])
244}
245
246pub fn register(registry: &mut BehaviorRegistry) {
248 let mut reg = |name: &str, title: &str, desc: &str, f: AdditionalQuestsFn| {
249 registry.register_additional_quests(
250 BehaviorMeta {
251 name: name.to_string(),
252 category: BehaviorKind::AdditionalQuests,
253 title: title.to_string(),
254 description: desc.to_string(),
255 },
256 f,
257 );
258 };
259
260 reg(
261 "loop_finish_advance",
262 "Доп. квесты: завершить и продвинуть loop",
263 "on_finish_regular_loop_task (no-op) + advance_loop; общий для регулярных loop-квестов.",
264 loop_finish_advance,
265 );
266 reg(
267 "loop_finish_advance_first_crystals",
268 "Доп. квесты: завершить/продвинуть + флаг первых кристаллов (019a6ef3)",
269 "finish + advance, затем SetCustomValue(loop_tasks.first_loop_skill_crystals_received, 1).",
270 loop_finish_advance_first_crystals,
271 );
272 reg(
273 "loop_finish_prepare_advance",
274 "Доп. квесты: завершить + prepare(RNG) + продвинуть (019bdb53)",
275 "on_finish_regular_loop_task + prepare_loop (RNG) + advance_loop.",
276 loop_finish_prepare_advance,
277 );
278 reg(
279 "loop_prepare_advance",
280 "Доп. квесты: prepare(RNG) + продвинуть (019cd269)",
281 "prepare_loop (RNG) + advance_loop.",
282 loop_prepare_advance,
283 );
284
285 let milestones: &[(&str, &str, AdditionalQuestsFn)] = &[
287 ("aq_milestone_level_1", "level=1", milestone_level::<1>),
288 ("aq_milestone_level_2", "level=2", milestone_level::<2>),
289 ("aq_milestone_level_3", "level=3", milestone_level::<3>),
290 ("aq_milestone_level_4", "level=4", milestone_level::<4>),
291 ("aq_milestone_level_5", "level=5", milestone_level::<5>),
292 ("aq_milestone_level_6", "level=6", milestone_level::<6>),
293 ("aq_milestone_level_7", "level=7", milestone_level::<7>),
294 ("aq_milestone_level_8", "level=8", milestone_level::<8>),
295 ("aq_milestone_level_9", "level=9", milestone_level::<9>),
296 ("aq_milestone_level_10", "level=10", milestone_level::<10>),
297 ("aq_milestone_level_11", "level=11", milestone_level::<11>),
298 ("aq_milestone_level_12", "level=12", milestone_level::<12>),
299 ("aq_milestone_level_13", "level=13", milestone_level::<13>),
300 ("aq_milestone_level_14", "level=14", milestone_level::<14>),
301 ("aq_milestone_level_15", "level=15", milestone_level::<15>),
302 ("aq_milestone_level_16", "level=16", milestone_level::<16>),
303 ("aq_milestone_level_17", "level=17", milestone_level::<17>),
304 ("aq_milestone_level_18", "level=18", milestone_level::<18>),
305 ("aq_milestone_stage_1", "stage=1", milestone_stage::<1>),
306 ("aq_milestone_stage_2", "stage=2", milestone_stage::<2>),
307 ("aq_milestone_stage_3", "stage=3", milestone_stage::<3>),
308 ("aq_milestone_stage_4", "stage=4", milestone_stage::<4>),
309 ("aq_milestone_stage_5", "stage=5", milestone_stage::<5>),
310 ("aq_milestone_stage_6", "stage=6", milestone_stage::<6>),
311 ("aq_milestone_stage_7", "stage=7", milestone_stage::<7>),
312 ("aq_milestone_stage_8", "stage=8", milestone_stage::<8>),
313 ("aq_milestone_stage_9", "stage=9", milestone_stage::<9>),
314 ("aq_milestone_stage_10", "stage=10", milestone_stage::<10>),
315 ("aq_milestone_stage_11", "stage=11", milestone_stage::<11>),
316 ("aq_milestone_stage_12", "stage=12", milestone_stage::<12>),
317 ("aq_milestone_stage_13", "stage=13", milestone_stage::<13>),
318 ("aq_milestone_stage_14", "stage=14", milestone_stage::<14>),
319 ("aq_milestone_stage_15", "stage=15", milestone_stage::<15>),
320 ("aq_milestone_stage_16", "stage=16", milestone_stage::<16>),
321 ("aq_milestone_stage_17", "stage=17", milestone_stage::<17>),
322 ("aq_milestone_stage_18", "stage=18", milestone_stage::<18>),
323 ("aq_milestone_stage_19", "stage=19", milestone_stage::<19>),
324 ];
325 for (name, label, f) in milestones {
326 reg(
327 name,
328 &format!("Доп. квесты: веха {label} + advance"),
329 "SetCustomValue(loop_tasks.last_milestone.<k>, n) + advance_loop.",
330 *f,
331 );
332 }
333
334 let mimic_codes: &[(&str, AdditionalQuestsFn)] = &[
336 ("aq_mimic_next_item_0", mimic_next_item::<0>),
337 ("aq_mimic_next_item_1002", mimic_next_item::<1002>),
338 ("aq_mimic_next_item_1011", mimic_next_item::<1011>),
339 ("aq_mimic_next_item_2010", mimic_next_item::<2010>),
340 ("aq_mimic_next_item_3002", mimic_next_item::<3002>),
341 ("aq_mimic_next_item_4001", mimic_next_item::<4001>),
342 ];
343 for (name, f) in mimic_codes {
344 let code = name.rsplit('_').next().unwrap();
345 reg(
346 name,
347 &format!("Доп. квесты: next_mimic_item_code={code}"),
348 "SetCustomValue(next_mimic_item_code, <код>).",
349 *f,
350 );
351 }
352
353 let redirects: &[(&str, &str, AdditionalQuestsFn)] = &[
355 (
356 "aq_redirect_to_kill_3_enemies",
357 "019cd267",
358 redirect_to::<REDIRECT_KILL_3_ENEMIES>,
359 ),
360 (
361 "aq_redirect_to_open_chest_6",
362 "019cd268-6b9e",
363 redirect_to::<REDIRECT_OPEN_CHEST_6>,
364 ),
365 (
366 "aq_redirect_to_sell_5_gear",
367 "019cd268-a523",
368 redirect_to::<REDIRECT_SELL_5_GEAR>,
369 ),
370 (
371 "aq_redirect_to_reach_level_3",
372 "019cd269",
373 redirect_to::<REDIRECT_REACH_LEVEL_3>,
374 ),
375 ];
376 for (name, target, f) in redirects {
377 reg(
378 name,
379 &format!("Доп. квесты: переключить loop task → {target}"),
380 "UpdateActiveLoopTaskId(<целевой loop-task квест>).",
381 *f,
382 );
383 }
384
385 reg(
387 "test_loop_task_1",
388 "Тест: loop_task=1 → 29540ca2",
389 "SetCustomValue(loop_task, 1) + UpdateActiveLoopTaskId(29540ca2).",
390 test_loop_task_1,
391 );
392 reg(
393 "test_loop_task_2",
394 "Тест: loop_task=2 → 9d3d2428",
395 "SetCustomValue(loop_task, 2) + UpdateActiveLoopTaskId(9d3d2428).",
396 test_loop_task_2,
397 );
398 reg(
399 "test_loop_task_0",
400 "Тест: loop_task=0 → 305300e2",
401 "SetCustomValue(loop_task, 0) + UpdateActiveLoopTaskId(305300e2).",
402 test_loop_task_0,
403 );
404 reg(
405 "test_loop_task_seed",
406 "Тест: выдать loop-task цикл (305300e2/29540ca2/9d3d2428)",
407 "SetCustomValue(loop_task, 0) + NewQuests([3 loop tasks]) + UpdateActiveLoopTaskId(305300e2).",
408 test_loop_task_seed,
409 );
410
411 register_default_loop_tasks(registry);
412}
413
414pub struct DefaultLoopTaskCtx;
417
418pub type DefaultLoopTaskFn = fn(&DefaultLoopTaskCtx) -> anyhow::Result<Uuid>;
420
421pub fn default_loop_task_const(_ctx: &DefaultLoopTaskCtx) -> anyhow::Result<Uuid> {
423 Ok(Uuid::from_u128(0x019cd267_a50f_7d1b_81a5_83147141fb8c))
424}
425
426pub fn test_default_loop_task(_ctx: &DefaultLoopTaskCtx) -> anyhow::Result<Uuid> {
428 Ok(Uuid::from_u128(0x305300e2_f432_4cbf_ad46_85386a9a9410))
429}
430
431fn register_default_loop_tasks(registry: &mut BehaviorRegistry) {
434 registry.register_default_loop_task(
435 BehaviorMeta {
436 name: "default_loop_task_const".to_string(),
437 category: BehaviorKind::DefaultLoopTask,
438 title: "Дефолтное loop-задание (константный uuid)".to_string(),
439 description: "Возвращает константный uuid loop-задания.".to_string(),
440 },
441 default_loop_task_const,
442 );
443 registry.register_default_loop_task(
444 BehaviorMeta {
445 name: "test_default_loop_task".to_string(),
446 category: BehaviorKind::DefaultLoopTask,
447 title: "Тест: дефолтное loop-задание (305300e2)".to_string(),
448 description: "Возвращает 305300e2 (фикстурный путь восстановления loop-task)."
449 .to_string(),
450 },
451 test_default_loop_task,
452 );
453}