# quests.ludic — RPG Module D: quests + objectives, driven by events (#59). # # A quest is data: a state plus a list of objectives (kind + needed count). Progress # is reported through Quest.notify — a game routes its existing gameplay signals # (a kill, a pickup, a tile entered, a dialog choice) into it, and matching active # objectives tick up; when all of a quest's objectives are met it auto-completes. # A global flag store (Dict) records world state for branching. All state is in # module Dicts/arrays, so world_save captures the journal for free. # # objective kind convention: 0 kill, 1 collect, 2 reach, 3 talk, 4 flag (extend # with your own kind ints + a Quest.notify from the matching listener). # quest state: 0 hidden, 1 available, 2 active, 3 done, 4 failed. const QUEST_MAX: int = 128 const OBJ_MAX: int = 512 event QuestStarted { quest: int = 0 } event QuestCompleted { quest: int = 0 } event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, need: int = 0 } # Quest.define(name) — register a quest (state hidden). Returns id. @Namespace(Quest) function quest_define(rpg_st: mut RpgState, name: pointer) -> int { if rpg_st.quest_n >= QUEST_MAX { return -1 } let id = rpg_st.quest_n rpg_st.quest_state[id] = 0 rpg_st.quest_n += 1 Dict.set(rpg_st.quest_reg, name, id) return id } @Namespace(Quest) function quest_id(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.quest_reg, name, -1) } # Quest.objective(quest_name, kind, target, need) — add an objective. @Namespace(Quest) function quest_objective(rpg_st: mut RpgState, quest_name: pointer, kind: int, target: int, need: int) -> int { let qid = quest_id(rpg_st, quest_name) if qid < 0 { return -1 } if rpg_st.obj_n >= OBJ_MAX { return -1 } let id = rpg_st.obj_n rpg_st.obj_qid[id] = qid; rpg_st.obj_kind[id] = kind; rpg_st.obj_target[id] = target rpg_st.obj_need[id] = need; rpg_st.obj_have[id] = 0 rpg_st.obj_n += 1 return id } # Quest.start(name) — activate a quest (emits QuestStarted). @Namespace(Quest) function quest_start(rpg_st: mut RpgState, name: pointer) -> void { let qid = quest_id(rpg_st, name) if qid < 0 { return } rpg_st.quest_state[qid] = 2 emit QuestStarted(quest: qid) } @Namespace(Quest) function quest_state_of(rpg_st: mut RpgState, name: pointer) -> int { let qid = quest_id(rpg_st, name) if qid < 0 { return -1 } return rpg_st.quest_state[qid] } # is every objective of a quest satisfied? function quest_all_done(rpg_st: RpgState, qid: int) -> int { var i = 0 while i < rpg_st.obj_n { if rpg_st.obj_qid[i] == qid { if rpg_st.obj_have[i] < rpg_st.obj_need[i] { return 0 } } i += 1 } return 1 } # Quest.notify(kind, target, amount) — report a gameplay signal. Every ACTIVE # quest's matching objectives tick up (target 0 on the objective = "any"); a quest # whose objectives are all met auto-completes. @Namespace(Quest) function quest_notify(rpg_st: mut RpgState, kind: int, target: int, amount: int) -> void { var i = 0 while i < rpg_st.obj_n { let qid = rpg_st.obj_qid[i] if rpg_st.quest_state[qid] == 2 { if rpg_st.obj_kind[i] == kind { if (rpg_st.obj_target[i] == 0) or (rpg_st.obj_target[i] == target) { if rpg_st.obj_have[i] < rpg_st.obj_need[i] { rpg_st.obj_have[i] += amount if rpg_st.obj_have[i] > rpg_st.obj_need[i] { rpg_st.obj_have[i] = rpg_st.obj_need[i] } emit ObjectiveProgressed(quest: qid, objective: i, have: rpg_st.obj_have[i], need: rpg_st.obj_need[i]) if quest_all_done(rpg_st, qid) == 1 { rpg_st.quest_state[qid] = 3 emit QuestCompleted(quest: qid) } } } } } i += 1 } } # ---- global flag store (branching / world state) -------------------------- @Namespace(Quest) function quest_flag(rpg_st: mut RpgState, name: pointer, value: int) -> void { Dict.set(rpg_st.quest_flags, name, value) } @Namespace(Quest) function quest_flag_get(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.quest_flags, name, 0) }