Open nigelrobbins3 opened 7 years ago
Or maybe we can get away with it by pulling actions out of events entirely.
// --- Game.js ---
// ...
while(!isGameOver()) {
dispatch(prepareAllYourActionsEveryone())
const actions = this.everyone.map((thing) => thing.preparedAction);
actions.sortBy(initiativeSorter);
actions.forEach((action) => {
action.validate();
action.perform();
}); // except a for loop so we can break on GameOver
}
// --- SomeThing.js ---
subscribe("PREPARE_ALL_YOUR_ACTIONS_EVERYONE", thingActionPreparer, this);
function thingActionPreparer(subscriber, context, event) {
this.preparedAction = this.prepareAction();
return {
validate() {
const validateTargetPayload = { target: this.preparedAction.target };
dispatch(pleaseChangeMyTargetIfYouWant(validateTargetPayload));
const target = validateTargetPayload.target; // could have been mutated
const validateBonusesPayload = {
target,
roll: this.preparedAction.roll,
bonus: this.getBonusesIKnowAbout(),
};
dispatch(pleaseUpdateMyBonuses(validateBonusesPayload));
// etc....
},
perform() {
// maybe unnecessary to have both
const performPayload = { ...this.buffsAndRollsAndWhatever };
dispatch(thisActionIsReallyReallyPerformingNow(performPayload));
dispatch(checkLifestealOrKillstreakOrWhatever()); // not sure how to get this still
};
}
The benefit in the second one is if a particular action comes along and needs to validate something it's easy to slot it into the action and add a listener on the relevant other Things. The drawback is that we still go in arbitrary "listener creation" order, but at least it's isolated more effectively. If two people want to change my bonus that's easy, just buff = oldBonus + newBonus
. If two people want to change my target then the last one wins, but maybe that's OK.
While attempting to design an action system which could properly handle delegating behavior to its listeners, I stumbled across some very dangerous problems. Take the following scenario:
Players: An Enchanter with Chaos (target randomly reassigns its target), a nearby Knight with an aura which prevents flanking bonuses on nearby allies (I'm 90% sure this was an ability, but it's not in my Google doc), and another player who is NOT adjacent to the Knight (let's make him a Thief).
Enemies: An archer and a mage. The archer is far away, the mage doesn't matter.
Define actions: the Knight and the Thief pick their nose. The mage will buff the archer, the archer will shoot the Thief (flanking bonus), and the Enchanter will cast Chaos on the archer. Mage and Enchanter go before the archer.
Perform actions:
We begin the problems:
BUT! What if the listeners were in a different order...
Now the DM has to explain that the Enchanter died even though he asked the Thief to roll for defense.
Technically this isn't completely accurate; Chaos is not a debuff so it would probably swap the target during its resolution, but there might be a duration version which behaves this way, and it still illustrates the point.
Essentially we have three steps:
PREPARE_ACTION
(get initiative, mostly).VALIDATE_ACTION
(right before we do it, check on the game state to apply weird things like buffs and passives)PERFORM_ACTION
(do the thing, get the damage, heal the lifesteal, etc. Probably Charisma?)The first and possibly third are straightforward. The second is full of complex interactions and might include additional actions (Trip, for example). I see two solutions. One, give up. We're boned, and we should just show the actions to the DM before using them to ask for any changes based on buffs, etc. Two, try to establish a hierarchy for listeners.
Maybe something like:
This assumes that there are only a few distinct classes which MUST follow after each other. It could be more than three, and adding a queue is easy, but if there are priority conflicts we're just completely doomed.