I have been not-so-secretly working on a Drupal 6 implementation of my original Game module, with a slightly different approach to things but without all the hooplah, design, or scaffolding. I'm starting with the smallest playable component, the combat engine, and working outwards from there. Having just finished a turn-by-turn, page-load-by-page-load, game-state-saving version of the combat engine which supports 1v1, 2v1, 2v2, etc., I'm starting to branch out into a few other areas, but wanted to jot down my notes on adding an API to the combat engine. Your comments are appreciated.
What I am looking for:
- Suggestions regarding hook naming and implementation (drupal_alter vs. module_*, etc.).
- Use case scenarios, whether they work or not with the proposed implementation.
What I am not looking for:
- Commentary on the combat rules themselves. They're "finished". End of discussion.
Here are the combat rules that we'll be adding hooks too:
1) At the beginning of a round, each player rolls a d20 for initiative.
The highest initiative roll goes first; if tied, everyone re-rolls.
2) In order of initiative, each player attacks with all their creatures.
3) Attacking creature targets opposing creature and chooses an attack.
4) Roll a d20 for this attack, add any relevant modifiers (from the
creature's own natural abilities, equipment, local modifiers, etc.),
and compare the final result to the opposing creature's armor or
defense (as dictated by the chosen attack).
* If a natural 20 was rolled (sans modifiers) on the attack roll, the
attack is considered a critical hit: the attack automatically hits.
5) If the attack matches or exceeds the opposing armor or defense, the
attack is considered a hit, and the attack's damage is subtracted from
the opposing creature's hit points.
* If a natural 20 was rolled (sans modifiers) on the attack roll, the
attack is considered a critical hit: the attack deals double damage.
6) Repeat until only one player remains.
And here are the hooks I've jotted down. All hooks receive the entire game/battle state to work with (probably by reference, similar to the nodeapi for certain operations). Hooks, by desire, can mess around with any part of the game.
- game_battle_init: Right after the battle has been declared; the battle state has just been initialized, parties have been loaded and stored, and things are about to commence. Question: should this be _init or _start? Why?
- game_battle_round_start: Before initiative has been determined.
- game_battle_turn_init: After initiative has been determined, before turn start.
- game_battle_turn_start: Before any mob has started their attack phase.
- game_battle_attack_start: The attacking mob has been selected. This would be the primary place for modules to hook in some AI - having a computer player know which target mob to attack, with which attack, and yadda yadda yadda.
- game_battle_attack_rolled($result_op):
$result_opwould contain one of "success", "miss", "critical success" or "critical miss". Allows modules to react to successful or missed rolls (like a "fate module" that adds in giggle-worthy descriptions of utter failures, etc.). - game_battle_attack_damage: The attack is successful and the initial damage has been determined. Allows modules to modify that damage output (perhaps a "dodge" roll can halve the damage, or the environment can increase it, etc.).
- game_battle_mob_condition($condition):
$conditionwould contain stuff like "active", "destroyed", "invisible", "confused", etc. Allows a module to listen for those conditions, or even set them (via another hook, etc.) - game_battle_party_destroyed: Fires when a party is destroyed completed.
- game_battle_turn_end, game_battle_round_end, game_battle_end: I'm not entirely too sure about these, but they would largely be the counterparts to the _start versions above. Garbage collection, duration checks, etc.
That's all fine and dandy, but how would you use them? Here are some examples. Your particular ideas, and their application toward this API, would be most ideal, as they allow me to see what you WANT to do, along with what you CAN, and CAN'T, do.
- Arenas: An arena plugin would sit on
battle_start, adding descriptive messages about the battleground the player's are fighting in. It could also modify the game state based on the characters (an "underwater" arena could deal 10 starting damage to fire-based creatures, etc.) - Flaming Weapon: A flaming weapon plugin could set a weapon on fire, and listen for
battle_attack_damage, adding 5 damage to any and all attacks by an enflamed implement. Similarly, an addon could add a new "Dodge" trait which would allow mobs to dodge a successful attack, dealing only half damage. - Charges: Some attacks are so powerful that they can only be used 2 or 3 times. A charges plugin would hook_form_alter() as necessary to prevent a player from choosing a depleted attack, and would sit on
battle_turn_endto remove charges whenever a particular attack could be used. Certain capabilities could recharge an attack, and the plugin could listen on most any other hook to do that. - Poison: If a weapon can poison a character for 3 turns, then this addon would listen on
battle_attack_rolled('success')to add a duration to the current mob's state. Duringbattle_turn_start, it'd check for the poison state, and deplete the mob's hit points if it was poisoned. Onbattle_turn_end, it would reduce the remaining duration, allowing the poison to eventually expire. - Frozen: If a frozen ray hits a mob, not only is the mob encased in ice preventing his attacks, but he takes 10 shivering damage a turn. A Frozen plugin would process cold damage on
battle_turn_start, as well as removing the frozen critter from the active list of attacking mobs.
This is all napkin design whilst I was babysitting. Rip me apart.

Comments
Great work! Can't wait to
Great work! Can't wait to try out the system as it evolves.
I think as long as you pass the combat state by reference all along, it allows for much flexibility. You hint at defenses, for instance, and as long as the hook gives access to everyone involved, it seems as though it would allow even some unusual situations.
game_battle_attack_damagefrom the target's force field object, I assume. Similarly, duringgame_battle_round_end, the force field would replenish all its points for the next round (and deplete an energy charge point).game_battle_attack_startwould need to know how many kobolds were in the fighting group.game_battle_attack_rollednullify the result, change the message, or wouldgame_battle_attack_starthijack the entire attack?What's the intended difference between turns and rounds? There's
game_battle_turn_startandgame_battle_turn_end, thengame_battle_round_endbut no matchinggame_battle_round_start.I'm sure I'll come up with more examples and questions later. But work interferes right now.
Aaron Winborn
Drupal Multimedia (book)
AaronWinborn.com (blog)
Advomatic (work)
Aaron Winborn
Drupal Multimedia (my book, available now!)
AaronWinborn.com
Advomatic
Yep, hooks would receive the
Yep, hooks would receive the /entire/ game state. Whilst you wouldn't be able to see other battles in-progress (long story short: multiple battles can be in progress at any one time, some battles are shared with other players, and only the "this page load" battle is loaded into the game state), you'd be able to see everything else in the game state.
"This would happen by invoking game_battle_attack_damage from the target's force field object, I assume." Roughly speaking - there's no concept of "item objects" planned. Instead things boil down into, roughly, three concepts: actions, modifiers, and callbacks. There's no "Greatsword" item, for example, instead, there's a "Greatsword" action that deals 10 damage. There may not be any "Magic Ring" item, but there's certainly a "thing" that modifies damage by +10. Anything else would be handled via an attached callback (which, as with hooks, would receive the entire game+battle state to do crazy things with). Your force field "object" would, I think, ideally be an attached callback (since it is always-on, and not an "action" and doesn't have any "simple" modifiers). [Note, again, none of this is finalized. The short answer is: yes, your proposed force field would be entirely possible in the planned API.]
Kobolds: no problem there. Game state contains a list of all party members of all players in this battle.
"Would game_battle_attack_rolled nullify the result, change the message, or would game_battle_attack_start hijack the entire attack?" That's a good question. All dice rolls are logged so, as planned, you would always see this in the log:
Attack roll for Longsword: 4 (+12): 16.. Now, that's not a critical success or critical miss, so the next step would be to determine if it's a regular success or miss. Once that's determined, we call the hook. The current dice roll is logged into the game state, so the hook would receive this game state with the current dice roll for the attacker. The hook could do a lot of things at this point: it could a) add its own log message, b) modify the dice roll. For your ghost example, you could make the dice roll a critical miss (set it to 1) or just a regular miss (set it to 0). Once you return, currently, another log message is generated:Longsword attack roll was 16 vs. armor (20)..So, here's one potential logging scenario:
[core] Attack roll for Longsword: 4 (+19): 23[ghost hook receives 23, modifies, logs] The ghost's ethereal nature causes this attack to miss!
[core] Longsword attack roll was 0 vs. armor (20).
The problem with the above is multifold: if ghost.module is fake changing this "success" hook to actually "mean" a failure (by setting the dice roll to 0 or 1), then all module hooks that come after ghost.module are going to think that a 0 or a 1 were a "success". That'd cause retardation.
So, it looks like we really need two hooks here:
This would let you treat ghosts as unhittable (by changing the result of the roll in
game_battle_attack_roll), but also properly inform other modules that this is a determined miss (via the followup hook ofgame_battle_attack_status('miss'). Note, however, that due to the nature of hooks, a future hook could take your ghost modification of (attack roll: 0 or 1), decide that fate shines on the player this day, and adds +30 to it, allowing the attack to hit. Such would be the nature of hooks. There is currently nothing planned that would allow a hook to enforce a critical hit or miss (if you wanted to get retarded, you could, of course, have your ghost.module checkgame_battle_attack_damageand set damage to 0, but I think that is a road to modules that compete with each other; that wouldn't be healthy.With aLLlL that said (which, I think, is quite useful: we definitely need two hooks for attack rolls), your ghost problem might have been solvable as you've mentioned: use
game_battle_attack_startto compare the attacker (human) vs. defender (ghost), and figure out someway to make the attack an automatic miss (perhaps as simple as removing the human's "target" from play for this turn, which would make the combat engine short circuit due to a missing target).Finally, a round starts with
Finally, a round starts with an initiative check, and ends when all players have taken their turns.
game_battle_round_start does exist above; you just missed it. You may instead be talking about the lack of game_battle_round_init - that doesn't exist because literally nothing happens between game_battle_init and game_battle_round_start. A game_battle_turn_init exists, however, to allow a chance for the initiative order to be changed (which would happen after game_battle_round_start but before game_battle_turn_start).
Incidentally, it looks like
Incidentally, it looks like this state will be passed around via a drupal_alter (so all hook names would have "_alter" appended to them), since I'd like things to be worked on by reference, instead of returning the state (as I fear the array merge on the complicated game state)...