sIKE23 / Mage-Wars

Mage Wars for OCTGN
7 stars 5 forks source link

Create Spell Dictionary and reroute everything through it #183

Open ACG8 opened 9 years ago

ACG8 commented 9 years ago

I think we have just about reached the limit of what we can do by reading the set.xml files. To continue with the automation of the game's features, I think we will need to create our own spell definitions.

I am proposing a dictionary of spells. Each spell will contain the stats of that spell, including its special, unique effects encoded by functions, buffs that it provides, etc. These functions and buffs will be bundled with instructions to specified when they are used, and on which targets (similar to the events system that OCTGN has). For instance, a creature might have an effect that triggers when it receives flame damage, in which case any game effect that causes flame damage will see that trigger in the dictionary and call the associated function. Or it might provide a buff to all bird creatures in its zone, and will have flags indicating that the ability applies to creatures in that zone that have the bird subtype.

I have a decent idea of how to implement this, and I also think it may be possible to automate a significant portion of the work. This is definitely a Q2 project, at least, and will require a lot of work. The benefit is that it will make automating the rest of the game MUCH easier, and will reduce upkeep of code for new releases by a lot.

It may be possible to write the functions directly in the dictionary, which would be great for creatures with unique effects. No guarantees yet, though; I am still figuring this part out.

Once the new spell dictionary is created, we can see about modifying all existing functions to go through it instead of the set.xml. This will result in significant gains in speed, since we can store data in the spell dictionary in its processed form, rather than having to process it each time we want to read it. I think ideally, we should use XML data only for proxy generation.

Thoughts?

sIKE23 commented 9 years ago

Get me a format for what we are wanting to do and lets start with a small set like CoK or FiF and see how that works?

ACG8 commented 9 years ago

I'm getting close to a workable format. I think the each creature dictionary should store basic properties of the creature and also functions, tied to named events. Buffs will be handled as events that update a global "gameState" dictionary, containing all the information on the present state of the game. Each time a player makes a choice, the game state will be updated; this should greatly reduce lag because it will usually happen during a player action. The BC can then be rerouted through the gameState dictionary, as opposed to passing dictionary objects containing the attacker and defender a bunch of times (not to mention updating those between each combat step).

Here's an outline of my format idea at the moment, which may change as I figure out better ways to do this:

spellDict['cardname'] = {
Name:       <str>
GUID:       <str>
Type:       <str>       (possibly treat walls like other conjurations?)
Cost:       <int>       (use 0 and have event onComputeCost for X type costs)
Subtypes:   [<str>,...]
Traits:     [<dict>,...]    (treat attributes as traits too. X Life = Innate Life +X, X Armor = Armor +X, X Channeling = Channeling +X).
Defenses:   [<defenseDict>,...]
Actions:    [<actionDict>,...]
Damage Barrier: <attackDict>            (omit if no damage barrier)
<<Events>>  <func>
OnResolve:  (called when spell is cast and resolved successfully)
OnDestroy:  (called when spell is destroyed)
OnDoubleClick:  (called when player double-clicks on spell while face-up)
OnMarkerPlaced: (called when a marker is placed on spell)
OnTargeted: (called when spell is targeted by another spell)
OnComputeCost:  (called when the cost of this spell is computed)
OnComputeGameState: (called when the game state is computed. Buffs are encoded here)
OnUntargetedAction: (called when an untargeted TAB is called on this card)

The functions tied to events accept a dictionary as their argument, to allow for a one-argument function. Then you just unpack the dictionary in the 

function. For example:

BlueGremlinOnUntargetedAction(dict):
    source = Card(dict.get('Source'))
    <function that asks if you want to pay 1 mana to gain fast/teleport>

By default, a targeted action is a plain attack menu. Creatures with other options receive those as well via actions in the action dictionary

#Actions are things a creature can use their action for, or that a conjuration can do between action phases#
<actionDict> = {
Range:      [<int>,<int>]           (min,max. <nonetype> if non-targeted)
Target:     <str>               (options: Object,Zone,Zone Border,None; <noneType> if none)
Effect:     <func>              (attacks call attack function, others call a locally defined function
Argument:   <dict>              (dictionary-type argument appropriate to passed function)

<defenseDict> = {
Uses:       <int>               (<noneType> if infinite)
Minimum:    <int>
Restriction:    <str>='No Melee'/'No Ranged'
<<Events>>  <func>
OnGetDefenses:  (called when determining what defenses are available, e.g. Tarok's limitations
OnUse:      (called if defense is used, e.g. forcemaster's deflect)
OnSuccess:  (called if defense succeeds, e.g. spiked buckler)
OnFailure:  (called if defense fails)
}
ACG8 commented 9 years ago

I decided to move the generator to the dev dropbox folder, since it isn't needed in the game files at all.

Right now, I am considering a few possible ways to handle information encoding. Maybe you can weigh in with your opinion on some of these.

diceBasedHeal(argument)

We want Asyran Cleric to be able to call this function for 2 dice when declaring an action. So we package the following tuple in his dictionary:

...
'onSpecialAction1` : (diceBasedHeal,{Dice : 2})
...

(right now, I am thinking of handling unique creature actions as a list of special action names which is stored as a list in some dictionary parameter, e.g. specialActions: ["Healing Light"] in the case of the cleric. The names of the special actions would show up in the action list (along with attacks and generic actions like guarding), and then if one is chosen it results in calling the function associated with that onSpecialAction* entry in the spell dictionary. This is kind of a long parenthetical)

Then, when specialAction1 is called, the argument dictionary (packaged with a few other pieces of information such as the source and target of the ability) is passed to the generic diceBasedHeal function.

Target Restrictions : ["not 'Mage' in Subtype","Living in Traits","Type=='Creature'"]

Then, when running a targeting legality check, you would iterate over the (string) elements and return False if any one of them returns false under eval()

sIKE23 commented 9 years ago
  1. I think that makes sense, healing via attack dice you should be able to roll an attack and then if it is [Healing] the target will += life instead of -= life am I missing something?
  2. That makes sense though I wonder how many exceptions you will have to put in for subtype == Mage instead of if type = Mage: type == Creature type of coding

3: Brilliant!

On Wed, Apr 8, 2015 at 3:06 PM, ACG8 notifications@github.com wrote:

I decided to move the generator to the dev dropbox folder, since it isn't needed in the game files at all.

Right now, I am considering a few possible ways to handle information encoding. Maybe you can weigh in with your opinion on some of these.

  1. Unique abilities will be handled with one-off functions coded specifically for that card. Obviously, I want to make as few of these functions as possible, and to reuse code as much as possible. At the moment, I am thinking of packaging functions in , pairs, where is a dictionary object containing specific parameters for generic functions, allowing us to reuse general purpose functions for abilities that are used a lot, such as dice based healing. For example, suppose we have some function:

We want Asyran Cleric to be able to call this function for 2 dice when declaring an action. So we package the following tuple in his dictionary:

... 'onSpecialAction1` : (diceBasedHeal,{Dice : 2}) ...

(right now, I am thinking of handling unique creature actions as a list of special action names which is stored as a list in some dictionary parameter, e.g. specialActions: ["Healing Light"] in the case of the cleric. The names of the special actions would show up in the action list (along with attacks and generic actions like guarding), and then if one is chosen it results in calling the function associated with that onSpecialAction* entry in the spell dictionary. This is kind of a long parenthetical)

Then, when specialAction1 is called, the argument dictionary (packaged with a few other pieces of information such as the source and target of the ability) is passed to the generic diceBasedHeal function.

  1. I am handling some things, such as Mage and Wall status, as subtypes, because I feel that is more appropriate. Mages and Walls are more properly creatures and conjurations (respectively), and their status as a Mage or Wall is better handled as a subtype.
  2. Targeting (and possibly other things) could be handled with filters, similar to the deathMessages. This time, though, the filters would be formatted as statements, and using them would involve calling eval() on each statement. For example, if you have a spell that targets a non-mage living creature, you could do:

Targets = ["not 'Mage' in Subtype","Living in Traits","Type=='Creature'"]

Then, when running a targeting legality check, you would iterate over the (string) elements and return False if any one of them returns false under eval()

— Reply to this email directly or view it on GitHub https://github.com/sIKE23/Mage-Wars/issues/183#issuecomment-91020542.

ACG8 commented 9 years ago
  1. I think that makes sense, healing via attack dice you should be able to roll an attack and then if it is [Healing] the target will += life instead of -= life am I missing something?

That is how I originally handled it, before I removed it from the BC. The problem with treating Heal as an attack is that attacks have a lot of baggage that comes with them, such as abilities that trigger on attacks, effects that boost attacks, etc. While I could code exceptions for all of those, I think it would be much cleaner to just make a separate function dedicated to healing effects.

  1. That makes sense though I wonder how many exceptions you will have to put in for subtype == Mage instead of if type = Mage: type == Creature type of coding

There may be some, certainly, but at the moment I already have loads of "if type in ["Creature","Mage"] checks, which just seems kind of ridiculous to me. I would much rather check if "Mage" in subtype

By the way, I intend to rewrite most of the BC from scratch to incorporate the spellDictionary. Much of the current code will be scrapped for new, better code, so building exceptions into existing code is not a major concern. The BC code should be much more concise once the spellDictionary is implemented, so it should not take as much time to rewrite it as it did originally (especially since I have a better idea of how to do it now).

sIKE23 commented 9 years ago
  1. Healing - great!
  2. Mage - will have to be careful of Creature exclusions that do not include Mages. Otherwise you persuasive argument makes sense.
  3. I had guessed it would be easier to rewrite than mod what is there, especially if you embed code in the creatures spell dictionary.

On Wed, Apr 8, 2015 at 3:48 PM, ACG8 notifications@github.com wrote:

  1. I think that makes sense, healing via attack dice you should be able to roll an attack and then if it is [Healing] the target will += life instead of -= life am I missing something?

    That is how I originally handled it, before I removed it from the BC. The problem with treating Heal as an attack is that attacks have a lot of baggage that comes with them, such as abilities that trigger on attacks, effects that boost attacks, etc. While I could code exceptions for all of those, I think it would be much cleaner to just make a separate function dedicated to healing effects.

  2. That makes sense though I wonder how many exceptions you will have to put in for subtype == Mage instead of if type = Mage: type == Creature type of coding

    There may be some, certainly, but at the moment I already have loads of "if type in ["Creature","Mage"] checks, which just seems kind of ridiculous to me. I would much rather check if "Mage" in subtype

By the way, I intend to rewrite most of the BC from scratch to incorporate the spellDictionary. Much of the current code will be scrapped for new, better code, so building exceptions into existing code is not a major concern. The BC code should be much more concise once the spellDictionary is implemented, so it should not take as much time to rewrite it as it did originally (especially since I have a better idea of how to do it now).

— Reply to this email directly or view it on GitHub https://github.com/sIKE23/Mage-Wars/issues/183#issuecomment-91032788.

ACG8 commented 9 years ago

Oops, accidentally closed this.