or, to make the stuff in that last post more explicit and code-backed:
(you could also think of it as a sequel to this post from a year and a half ago, because there i was talking about an earlier version of the encounter code. as you can see from this post, it's grown since then.)
so internally an
where
and is basically a list of every possible discrete thing that can happen in a game event -- an event can generate text narration, or text in a dialog frame, or give/remove PC items, or give body nodes, or present a list of choices, etc etc etc onto the more abstruse ones like
the type of
more specifically, each game encounter file provides a few things, most notably an instance to
which has to do with translating all the random haskell code that makes up an encounter into something that can be serialized (into JSON) and sent back to the client. from the bottom up:
so for example, the scene instance for the flame demon encounter looks like this:
(this is basically the pattern all the scene instances fall into, btw -- a case statement to link scene indexes with similarly-named functions, and then display/vardisplay being basically
SO if you read that prior post, here is the technical aspect of what i was calling 'subevents': if you have location X that could potentially store NPCs of type A, B, or C, then what in the world does its scene instance look like? specifically, given that an event is gonna return values of type
if you know haskell you might already be having a sinking feeling here
the answer is, of course, that
(to the best of my knowledge there's no major haskell library that provides an actual
so, while every existing scene instance took a form basically matching that example above, these parametric locations have some very different scene instances:
amazing
(also note the use of
(anyway this is interesting b/c the dryad code has a local variable named
(the next issue here is just the structural one of jumping into another encounter -- right now once you jump into an encounter you're basically stuck in the subtree until it ends; there's not really any jumping 'out'. but it's entirely POSSIBLE to explicitly change the link index of a subevent. but generally those aren't surfaced, and there's no functional infrastructure to do things like "oh okay run the subevent's description code, and then instead of the usual choices present "look around the base of the tree" and "talk to the dryad"", or "instead of 'leave' in the subevent exiting the event, have it jump back to the main event's choice prompt". i could handle things like that on a case-by-case basis, but it would involve surfacing more subevent code and generally be arbitrary and tangled, so once this is fully working from a code perspective the next issue is to think about what added structure would help with nesting events)
ANYWAY THOSE ARE SOME TECHNICAL DETAILS, HOPE YOU LIKED THEM
(you could also think of it as a sequel to this post from a year and a half ago, because there i was talking about an earlier version of the encounter code. as you can see from this post, it's grown since then.)
so internally an
Encounter is this:type Encounter i vars mob = BodyGlob -> mob -> [Event mob i vars]where
i is the scene-indexing type, vars is the local-variables type, and mob is the encounter type. Event itself looks like this:data Event e a l
= Narration [String]
| Narration2 EventReel
| Dialog String String [String]
| Dialog2 String String EventReel
| Dialog3 String EventReel EventReel
| ItemModify [Item]
| NodeGive TfNode
| Choices [(String, a)]
| Choices2 [(String, a, Maybe (Cond l))]
| Challenge Opponent [[Item]]
| FauxChoice String
| Jump a
| Shop String [Alchemy]
| Close
| DeactivateSelf
| InlineBranch (Cond l) [Event e a l] [Event e a l]
| SetFlag (Scope l) SetMod
| UnsetFlag (Scope l)
| SetEncounterData e
| PushEncounterData (Scope l)
| LoadAllSubData (Scope l) [String]
| PopToSubData (Scope l) [String]
| PeekLoadEncounterData (Scope l)
| Branch [(Cond l, a)] a
deriving (Show)and is basically a list of every possible discrete thing that can happen in a game event -- an event can generate text narration, or text in a dialog frame, or give/remove PC items, or give body nodes, or present a list of choices, etc etc etc onto the more abstruse ones like
PeekLoadEncounterData, which i don't even remember what it does.the type of
Encounter means that, on the server side, when generating the text for an encounter, each encounter has access to precisely two things: the pc body state, and the mob encounter state, both at the precise state they were in when the encounter request was sent (i.e., you can't change an encounter midway through and expect the later scene text to respond to that; all reachable encounter paths are generated once, at request time. when i first started coding the encounter system i made some attempts to change that, but as you might imagine that kind of flow analysis isn't super trivial so there's a certain amount of dead code devoted to path flow analysis.)more specifically, each game encounter file provides a few things, most notably an instance to
Scene:class ToJSON mob => Scene i vars mob | i -> mob vars, vars -> i mob where
link :: i -> Encounter i vars mob
display :: i -> String
vardisplay :: vars -> String
varread :: String -> Maybe vars
varread _ = Nothingwhich has to do with translating all the random haskell code that makes up an encounter into something that can be serialized (into JSON) and sent back to the client. from the bottom up:
varreadis dead code ostensibly used for reading the client-side encounter variable table, which is sent with the request but (currently) never usedvardisplayis to convert local/global variable references (which exist in code in a form likeLocal HasMetorGlobal FarmQuestActive) into plaintext so that the client-side encounter variable table has names to index its variables withdisplayis to render the scene index type, which is used because the actual ~scene api~ responds with a big lookup table of scene data, indexed by the scene index type (hence the name), so that also needs to be renderable as plaintextlink, which is the big one, and which determines how we look up scene parts by index on the server side. functions of typea -> bare effectively a total lookup table of the form[(a, b)], so, this is a lookup table withias the key andEncounter i vars mobas the value.
so for example, the scene instance for the flame demon encounter looks like this:
instance Scene Index Vars FlameEncounter where
link x = case x of
OpeningOptions -> openingOptions
TalkOpening -> talkOpening
Talk s -> talk s
ShopPrompt -> shopPrompt
Leave -> leave
FuckIntro -> fuckIntro
JerkOff from -> jerkOff from
BlowTop -> blowTop
MouthClimax -> mouthClimax
ThroatClimax -> throatClimax
Blow from -> blow from
SitAnal from -> sitAnal from
MountAnal from -> mountAnal from
CagePlay -> cagePlay
YeahFuck from -> yeahFuck from
Fist from -> fist from
Reject -> reject
Bye -> bye
Uncurse -> uncurse
display = fmap toLower . show
vardisplay = fmap toLower . show(this is basically the pattern all the scene instances fall into, btw -- a case statement to link scene indexes with similarly-named functions, and then display/vardisplay being basically
show.)SO if you read that prior post, here is the technical aspect of what i was calling 'subevents': if you have location X that could potentially store NPCs of type A, B, or C, then what in the world does its scene instance look like? specifically, given that an event is gonna return values of type
Event mob i vars, and it's not really feasible to edit the encounter code directly for every possible location that might store an NPC, how do we change those values around to basically turn events on one mob/index/vartable into events on a DIFFERENT mob/index/vartable?if you know haskell you might already be having a sinking feeling here
the answer is, of course, that
Event mob i vars forms a TRIFUNCTOR, and can be mapped over to convert values that match one Scene instance into values that match a different one. here:triMap :: (e -> f) -> (a -> b) -> (l -> m) -> Event e a l -> Event f b m
triMap ef af lf e = case e of
Choices cs -> Choices $ (\(s, a) -> (s, af a)) <$> cs
Choices2 cs -> Choices2 $ (\(s, a, mcl) -> (s, af a, (fmap lf) <$> mcl)) <$> cs
Jump a -> Jump $ af a
InlineBranch cl e e' -> InlineBranch (lf <$> cl) (triMap ef af lf <$> e) (triMap ef af lf <$> e')
SetFlag sl m -> SetFlag (lf <$> sl) m
UnsetFlag sl -> UnsetFlag (lf <$> sl)
SetEncounterData e -> SetEncounterData $ ef e
PushEncounterData sl -> PushEncounterData (lf <$> sl)
LoadAllSubData sl s -> LoadAllSubData (lf <$> sl) s
PopToSubData sl s -> PopToSubData (lf <$> sl) s
PeekLoadEncounterData sl -> PeekLoadEncounterData (lf <$> sl)
Branch cs a -> Branch ((\(cl, a') -> (lf <$> cl, af a')) <$> cs) (af a)
Narration s -> Narration s
Narration2 e -> Narration2 e
Dialog i n s -> Dialog i n s
Dialog2 i n e -> Dialog2 i n e
Dialog3 i n e -> Dialog3 i n e
ItemModify is -> ItemModify is
NodeGive tf -> NodeGive tf
Challenge o is -> Challenge o is
FauxChoice s -> FauxChoice s
Shop s as -> Shop s as
Close -> Close
DeactivateSelf -> DeactivateSelf(to the best of my knowledge there's no major haskell library that provides an actual
Trifunctor class that i could instance, so i was just like 'hey whatever' and decided to just write the map function as a standalone.)so, while every existing scene instance took a form basically matching that example above, these parametric locations have some very different scene instances:
data CedarTree = CDryad Dryad | CNature NatureSpirit | CUnicorn Unicorn
deriving (Eq, Ord, Show, Read)
data Index = DIndex Dryad.Index | UIndex Unicorn.Index | NIndex Nature.Index
deriving (Eq, Ord, Show, Read)
data Var = AsDryad Dryad.Vars | AsUnicorn () | AsNature ()
deriving (Eq, Ord, Show, Read)
asDryad :: Event Dryad Dryad.Index Dryad.Vars -> Event CedarTree Index Var
asDryad = triMap CDryad DIndex AsDryad
asNature :: Event NatureSpirit Nature.Index () -> Event CedarTree Index Var
asNature = triMap CNature NIndex AsNature
asUni :: Event Unicorn Unicorn.Index () -> Event CedarTree Index Var
asUni = triMap CUnicorn UIndex AsUnicorn
instance Scene Index Var CedarTree where
link f = case f of
DIndex di -> \b c -> case c of
CDryad d -> asDryad <$> link di b d
_ -> error "bad cedartree scene"
UIndex ui -> \b c -> case c of
CUnicorn u -> asUni <$> link ui b u
_ -> error "bad cedartree scene"
NIndex ni -> \b c -> case c of
CNature n -> asNature <$> link ni b n
_ -> error "bad cedartree scene"
display = fmap toLower . show
vardisplay = fmap toLower . showamazing
(also note the use of
() here -- generally that's what i use for encounters that don't have any local variables. you might think that using () multiple times in a scene instance violates the vars -> i mob fundep in the Scene definition, and you would be correct. but because haskell doesn't exhaustively check for overlapping instances, and since (generally) each scene is in a separate compilation unit, and since i never plan on trying to uniquely identify a scene by vardisplay (), i think i'm fine.)(anyway this is interesting b/c the dryad code has a local variable named
Met, that's used to determine if you're revisiting a dryad. in this encounter, rather than Met being set and read to, a completely different variable, AsDryad Met, is set and referred to. since that's ultimately what being able to (tri)map over events means: that it's possible to rewrite index tables and vartables to break the correspondence with one scene while creating a new correspondence with another scene. this doesn't yet work 100% for reasons that i'm unsure about, and i think that might just be "the events i've written are poorly-structured to handle having a nested entry point", but it's mostly working. for example, in the current code, the npcs in the cedar tree location get their descriptions printed twice: once because i have the cedar tree opening narration describe their contained npc before jumping into the npc's scene, and the other... in the contained npc's scene.)(the next issue here is just the structural one of jumping into another encounter -- right now once you jump into an encounter you're basically stuck in the subtree until it ends; there's not really any jumping 'out'. but it's entirely POSSIBLE to explicitly change the link index of a subevent. but generally those aren't surfaced, and there's no functional infrastructure to do things like "oh okay run the subevent's description code, and then instead of the usual choices present "look around the base of the tree" and "talk to the dryad"", or "instead of 'leave' in the subevent exiting the event, have it jump back to the main event's choice prompt". i could handle things like that on a case-by-case basis, but it would involve surfacing more subevent code and generally be arbitrary and tangled, so once this is fully working from a code perspective the next issue is to think about what added structure would help with nesting events)
ANYWAY THOSE ARE SOME TECHNICAL DETAILS, HOPE YOU LIKED THEM