Outcomes and proposals
Record how a narrative moment ended, once, so another mod can follow it up once; and ask several people something only they can all agree to.
Outcomes
A story beat that ends one of several ways (an evening that ends in a goodnight or a longer talk, a confrontation that is settled or walked out of) is an outcome: one event id, the people in it, begun before the choice and committed once.
lualocal M = "ul_AfterMidnight"
local eid = "evening_" .. a:id() .. "_" .. n
local why = Outcomes:begin(M, eid, { kind = "narrative", people = { a } })
if why then return why end -- "That event already has an outcome."
Game:Choice(1, nil, "Say goodnight")
Game:Choice(2, nil, "Stay and talk")
local i = Game:GetChoice() -- the scene yields here
local gone = Outcomes:check(M, eid) -- still everyone here?
if gone then return gone end -- "Ana is no longer available." (now unavailable)
Outcomes:commit(M, eid, i == 1 and "a warm goodnight" or "private conversation")
if i == 2 then Outcomes:fade(1.5) end
- Begin before, check after. A choice yields: while it is open, the player can block the contact, or the person can be deleted.
checkafter the choice answers why the moment cannot go on and closes it asunavailable. - Committed once.
commitreturnstruethe first time andfalseafterwards, so a scene that runs twice (a reload, a second click) never applies its consequences twice.cancelcloses it without a result (the player left it undecided). - A pending outcome does not survive a load. If the game is saved and reloaded in the middle of the choice, the outcome comes back
cancelled("cancelled on load"): the scene that was waiting for it is gone. kindis"narrative"(the default) or"session". A narrative ending is text: it is not an animation session and applies no session consequences.commit(mod, id, result, data, atHours)takes an optional table of strings (data) and a time; without one, now. Use the time when migrating an older save's events.
Follow-ups
Another mod reads completed outcomes of a source mod and marks each handled for itself, once:
lualocal SELF, SOURCE = "ul_MorningAfter", "ul_AfterMidnight"
local list = Outcomes:followUps(SELF, SOURCE, person) -- oldest first, not handled by SELF
local o = list[1]
if o and Game:GetHoursPassed() >= o.closed + 12 then
if Outcomes:handle(SELF, SOURCE, o.id) then -- true the first time only
-- the morning message, the rapport...
end
end
Two mods can follow up the same evening, each once. outcomeCommitted (module, event id, result) fires for each commit, through Events:on.
The fade
Outcomes:fade(seconds) darkens the screen and brings it back on its own after seconds (0.2 to 5, default 1.5). Nothing has to undo it, and a script that errors in between cannot leave the screen black.
Proposals
A proposal asks something of people (exclusivity, an open arrangement, more time together) and stays open until each of them answers. The proposer (the player by default) does not answer for anyone.
lualocal M = "ul_PrivateAgreements"
local why = Proposals:propose(M, "open_1", { kind = "open", people = { ana, cleo } })
...
Proposals:accept(M, "open_1", ana) -- still "proposed": Cleo has not answered
Proposals:accept(M, "open_1", cleo) -- "accepted"
Proposals:decline(M, "open_1", cleo, "not now") -- or "declined", at once
- Everyone concerned must be an adult contact who is not blocked;
proposesays who is not. - A new proposal of the same
kind, from the same mod, between the same people supersedes the open one (itsreasonis the new id). withdrawcloses it from the proposer's side. A participant who is blocked or deleted cancels it.- Rapport is not an answer: decide in your own script whether a person accepts, and call
acceptordeclinefor them then. Changing roles (partner, housemate) after an acceptance is your mod's job. proposalChanged(module, id, state) fires on each change.
| Call | Does |
|---|---|
Outcomes:begin(mod, id, { kind, people }) |
nil, or why not. |
Outcomes:check(mod, id) |
nil while everyone is there, else why (now unavailable). |
Outcomes:commit(mod, id, result [, data [, atHours]]) |
true once. |
Outcomes:cancel(mod, id [, reason]) |
true if it was pending. |
Outcomes:get(mod, id) |
{ module, id, kind, state, result, people, data, created, closed, handledBy } or nil; a cancelled or unavailable one carries the reason in result. |
Outcomes:followUps(consumer, source [, person]) |
Completed outcomes not handled by consumer. |
Outcomes:handle(consumer, source, id) |
true the first time. |
Outcomes:fade([seconds]) |
A fade out and back. |
Proposals:propose(mod, id, { kind, people, proposer, terms }) |
nil, or why not. |
Proposals:accept(mod, id, person) / decline(mod, id, person [, reason]) |
nil, or why not. |
Proposals:withdraw(mod, id [, reason]) |
true if it was open. |
Proposals:get(mod, id) |
{ module, id, kind, state, reason, proposer, people, accepted, terms, created } or nil. |
Proposals:list([mod [, person [, state]]]) |
Matching proposals. |
States: an outcome is pending, completed, cancelled or unavailable; a proposal is proposed, accepted, declined, withdrawn, superseded or cancelled. Closed records are kept (up to 300 outcomes and 200 proposals, oldest closed first).