95 lines
4 KiB
Text
95 lines
4 KiB
Text
# 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) }
|