A Tiny Defeat
As a follow-up to the Lupine Manifesto post, I thought I'd repost a slightly edited version of a message I wrote talking about a design for a Defeat-style mod.
The idea is to create a very small, extremely minimal defeat handler, with outcomes as plugins that can be added by other mods.
Basically, to take ALL the consequence handling out of the core defeat mod and provide a way to register arbitrary defeat handlers.
The most basic defeat handler is thus, nothing. You die.
That's the final fallback outcome if all else fails.
The base mod would come with some simple handlers, and some library functionality to do common tasks.
Other mods can then register into it via an API.
When defeat occurs, the core mod detects it, and contrives for the handlers to deal with it.
Handlers are all given a chance to prioritize themselves, and then they are given a chance to handle - though not all handlers may get a chance to handle, all get a chance to establish their priority, situationally, based on the conditions of the defeat.
Each handler decides for itself if it will handle the situation.
The handle/don't-handle logic is not part of the defeat mod, it just tries them in priority order, and the priorities are also calculated.
If we imagine it at an abstract level...
We have a DefeatState, which is the known state of defeat, including all the actors in play, and what side we think they're on (ally, neutral, enemy).
We pass that DefeatState to all registered handlers in no particular order, and they return a priority - this is their "bid" to handle the defeat.
We then refresh the DefeatState and test each handler in priority order, and ask it if it will *actually* handle the defeat.
The handlers check the DefeatState and make a final call on whether they will run.
If a handler runs, it can then terminate the defeat or let other handlers continue it, or it can re-open the bidding process and let handlers re-bid.
If the active handler allows continuation, lower bidding handlers may ALSO run, but they will not get a chance to do so until the current active handler is done.
Again, they will be handed a refreshed DefeatState at that point, and decide whether to skip, handle and terminate or handle and continue.
External mods can call a well defined API to register handlers, but the assumption is that you will quickly develop a reasonable set of handlers to do the basic stuff, like rapes, teleports, robberies etc.
For example, a handler might calm everything, giving you time to make a run for it. That handler would have a cooldown, so if you're defeated again, you fall back down the chain and get ... death.
And of course, there might be a rape handler, or several...
So, a given run might look something like this:
Defeat detected...
Enemies: draugr, draugr, draugr overlord, current healths 150, 250, 750 respectively.
Neutral: none
Allies: Lydia - current health 150
Cause: 0 player health
This then polls the handler list for priorities, passing in the defeat state...
The handler list, in this case is:
Death - PC is killed. Reload.
Bleedout - PC goes in bleedout. Enemies do not pacify if any combatants stand.
Robbery - PC's gear is taken.
PortToInn - PC is ported to a random nearby inn.
EnemyRape - PC is raped by enemies.
Imprisonment - PC is imprisoned at length and must escape or be released.
SecondWind - PC recovers full health and can restart the fight.
Given the conditions, each makes the following bids:
Death - always bids 0
Bleedout - bids 100
Robbery - bids 50
PortToInn - bids 40
EnemyRape - bids 90
Imprisonment - bids 50
SecondWind - bids 150
SecondWind wins and gets first chance to handle. It bid high because a follower was still standing.
Bleedout lost, though arguably it would be appropriate. Because the player explicitly enabled SecondWind, it does what it does.
SecondWind runs, restores the player health, and the event is over. Defeat is done...
Then 45 seconds later, defeat runs again, the player is down again, and this time so is Lydia.
Defeat detected...
Enemies: draugr, draugr, draugr overlord, current healths 150, 250, 750 respectively.
Neutral: none
Allies: Lydia - current health 0
Cause: 0 player health
Bids occur again:
Death - always bids 0
Bleedout - bids 100
Robbery - bids 50
PortToInn - bids 40
EnemyRape - bids 90
Imprisonment - bids 50
SecondWind - bids -1 (basically saying "will not handle")
Not only is SecondWind in cooldown, and cannot run, it sees Lydia is down. It returns -1 as its priority, it's not in the game, Death will always beat it.
Bleedout wins, and handles first.
When it's called again to see if it really will handle, it says yes.
Bleedout always bids high, and always says yes. Why? Because it is the initial pacify manager.
However, the core pacification functionality is in the defeat mod core. Any handler can (and should) use it.
Bleedout sees no active allies, it pacifies Lydia and the enemies and it spins.
If an enemy or ally wanders into range while it's spinning, it will pacify them.
After a few seconds delay, bleedout completes and CONTINUES. The next handler can run.
The next highest bid is EnemyRape.
EnemyRape checks to see if it can run, and it sees a bunch of pacified enemies with health, and a downed PC and follower.
It goes "great, set me up the rapes!"
It starts two sex scenes, one for the PC and one for Lydia.
While it's running, the PC's other follower wanders into range.
The main defeat handler catches this, and hands the event to the current active handler (EnemyRape).
EnemyRape has no enemies to rape the new follower, so it gives them a flee in panic package and forgets about them for now.
Once the rape is done, enemy rape terminates and continues.
Robbery and Imprisonment both bid 50 and are both candidates.
The defeat mod picks one at random to go first.
Robbery is picked. The player and follower's gear is taken.
Robbery CONTINUES almost instantly.
Imprisonment runs...
But let's imagine Imprisonment was picked first.
In this case, imprisonment may see the PC has not been robbed, and may start by calling Robbery itself, because it knows that handler is part of core and always exists.
It simply passes in the existing state and sees if robbery will run. It will, and so it can perform a robbery without having to duplicate any code.
Robbery - as we already know - finishes and continues almost immediately.
Imprisonment starts up the main imprisonment quest, nails dying Lydia to a cross near Valtheim Halls, and puts the PC in a cage.
Imprisonment terminates after starting up the quests and porting the player to their cell.
At the Papyrus level, how do you register a handler? How are they called?
Handlers are quests that share a common extension script.
The specific handler overrides the methods in this script.
To register, you cast your quest to the common extension type, and hand your quest object to the defeat mod. It puts you in a list, and can call the defined methods on the quest any time. It can also check if your quest is running or not. If your quest isn't running, it's always skipped. This allows handlers to be enabled/disabled in their own MCMs easily.
How do you detect defeat? Isn't that a complex problem?
Yes. And defeat detection also needs to work with plugins to some extent.
There are two ways to go about this.
In one case, the detection is entirely in another mod, and it simply calls a mod event to inform the defeat mod of it.
The more interesting case is where the defeat mod collects information about the ongoing combat and passes it to the registered defeat handlers to see if they think a defeat is in progress.
This is nothing more than another Function defined on the base quest type, that returns one of a defined set of defeat types, the first of which is "no defeat in progress".
The data used to detect defeats looks uncannily like the data used to handle them. It's the exact same defeat state, but without a cause: we have actor lists organised by disposition, and a curious mod can traverse these lists and decide whether to trigger a defeat, or possibly modify the actors somehow.
A mod that doesn't care about detecting defeats can simply return, which of course is what the base class implementation does.
38 Comments
Recommended Comments