[HOWTO] Random Starting Player, (Optional) Mana Burn

All suggestions and feature requests to improve wagic.
Sub forums Card Coding & Parser ,Game Mode ,Deck Editor
Locked
Gadget2006
Posts: 6
Joined: Tue Jun 22, 2010 5:17 am

[HOWTO] Random Starting Player, (Optional) Mana Burn

Post by Gadget2006 »

EDIT: The corrected guide has been uploaded, thanks to wololo for some important corrections,
my memory must have failed me with certain things about the rules regarding mana.


OK guys, I really love the Wagic project, it's absolutely fantastic! However, I always had a little
problem with it, and the problem was in the basic rules compliance. There were some things that I
always wanted to see implemented in it, but since they were not implemented so far, I decided to try
and tinker with the source code and implement them myself. I used the latest post-0.12.1 SVN as a
base for my experiments, and I've more or less succeeded so far.

Here are the goals of this rules compliance patch, so that you can see what you will hopefully have if
you complete the steps in this guide
:

1. Allow the starting player to be chosen randomly at start, thus emulating the flip of a coin,
instead of the always giving the priority to the human player. This, however, as of right now should
not apply to the Story Mode, because it would break it.

2. (OPTIONAL) Implement the mana burn rule, whereby all the mana that was still in the mana pool at
the end of the phase would not just go away, but instead damage the offending player. This rule was
apparently taken out with the invent of Magic 2010 (thanks wololo!), but if you want this old-school
option, I'll teach you how to implement it.

There are as of yet the following problems with the solution I propose below:

- I haven't yet found an elegant way to check for the current game mode from Rules.cpp (e.g. detect
whether we are in Classic game mode or Story game mode), thus I had to resort to implementing a
static parameter in GameApp just for that reason. It's very clunky and hackish, so if there's a
better way to do it, please tell me or propose an alternative solution).

- We won't be adding any new animations per se, so you won't see a coin flip itself - yes, it means
no beautiful animation of a coin being tossed in the air and landing on your MTGish table - but at
least the starting player WILL be randomized.

- I also want to implement the Mulligan rule, but as of right now I haven't gotten to it. Also, it's
relatively low priority right now due to my time constraints.

- This patch hasn't yet extensively been tested (though I successfully finished several games by now
without any problems), so it MIGHT, in fact, break your Wagic. So make backups and don't tell me I
didn't warn you. :)

*************** AND HERE WE GO ***************

You will need to obtain the source code from the SVN repository and you have to know how to compile
it and how to work in your favorite IDE, such as Visual Studio. If you don't know some of this or if
you don't feel comfortable working with the code, or if words such as programming or C++ make you
feel uneasy, stop at this point and read the Wiki for the compilation guide. That being said,
here we go.

STEP 1. IMPLEMENTING A WAY TO DETECT THE GAME MODE (HACKISH!)

1.1. Now, let's turn to our second problem - implementing a way to randomize the starting player. Before we
can do that properly, however, we need to make sure we can check whether we are in the Story mode or not.
We need to avoid randomizing the player in the story mode, at least for now, because it looks like the
Story mode is planned with the fixed starting player in mind. If that changes later on, I'll try to update
this guide accordingly. As I said, the current solution is not at all elegant and is hackish, and may
actually cause problems (even though it didn't so far for me), so bear with me and if you know of a better
way to do this, please let me know. So, first, let's open GameApp.h (in the "Header Files") and introduce
a new property to the GameApp class, which we can use from anywhere to see if we're in Story mode or not.
Around lines 60-70, look for the following line:

Code: Select all

int gameType;
1.2. Don't do anything with that line, but instead, insert this one right under it:

Code: Select all

static int StoryMode;
1.3. We'll use the StoryMode parameter elsewhere to check whether we are in story mode or not. Now, open GameApp.cpp (in "Source Files"
this time) and look for the following line:

Code: Select all

MTGAllCards * GameApp::collection = NULL;
Under that line, insert the following one:

Code: Select all

int GameApp::StoryMode = 0;
1.4. By doing that we initialize our newly made parameter with a zero so that the game is considered not to be in the story mode by
default. Now, let's make sure the game actually sets this parameter depending on the game mode that was chosen. Open GameStateMenu.cpp
and look for the definition of the function ButtonPressed. Inside that function, look for the following large block of code that sets
certain stuff in the game engine depending on which game mode the user chooses:

Code: Select all

  case SUBMENUITEM_CLASSIC:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_CLASSIC;
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_MOMIR:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_MOMIR;
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_RANDOM1:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_RANDOM1;
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_RANDOM2:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_RANDOM2;
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_STORY:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_STORY;
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

#ifdef TESTSUITE
  case SUBMENUITEM_TESTSUITE:
	mParent->players[0] = PLAYER_TYPE_TESTSUITE;
	mParent->players[1] = PLAYER_TYPE_TESTSUITE;
	subMenuController->Close();
	currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
	break;
#endif
1.5. We need to add an appropriate set up of our StoryMode parameter to each of the "case" blocks in the above block of code - for the
Story Mode, we'll set it to "1" in order for us to be able to tell that we're in the Story mode and we don't need to randomize the
starting player. For all other modes, we'll set it to "0" so that the starting player is randomized. Take a look at the modified code
block below and carefully insert the lines marked with the "++" comment into the appropriate locations in your file:

Code: Select all

  case SUBMENUITEM_CLASSIC:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_CLASSIC;
    GameApp::StoryMode = 0;     // ++ You need to insert this! ++
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_MOMIR:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_MOMIR;
    GameApp::StoryMode = 0;     // ++ You need to insert this! ++
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_RANDOM1:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_RANDOM1;
    GameApp::StoryMode = 0;     // ++ You need to insert this! ++
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_RANDOM2:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_RANDOM2;
    GameApp::StoryMode = 0;     // ++ You need to insert this! ++
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

  case SUBMENUITEM_STORY:
    this->hasChosenGameType = 1;
    mParent->gameType = GAME_TYPE_STORY;
    GameApp::StoryMode = 1;     // ++ You need to insert this (note the "1")! ++
    subMenuController->Close();
    currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
    break;

#ifdef TESTSUITE
      case SUBMENUITEM_TESTSUITE:
	mParent->players[0] = PLAYER_TYPE_TESTSUITE;
	mParent->players[1] = PLAYER_TYPE_TESTSUITE;
        GameApp::StoryMode = 0;     // ++ You need to insert this! ++
	subMenuController->Close();
	currentState = MENU_STATE_MAJOR_DUEL | MENU_STATE_MINOR_SUBMENU_CLOSING;
	break;
#endif
And now we're done with this step.

STEP 2. MAKING THE GAME RANDOMIZE THE STARTING PLAYER.

2.1. Alrighty, now the actual fun part - making the game make use of our parameter implemented above, and making it actually randomize
the starting player, as we planned. To achieve that, open your Rules.cpp file (in "Source Files"), and look for the function (which is
actually a "constructor" in proper C++ terms) which is defined as RulesState::RulesState(). In its original form it will look something
like this:

Code: Select all

RulesState::RulesState(){
  phase = Constants::MTG_PHASE_FIRSTMAIN;
  player = 0;
}
2.2. As you can see, in its original form it just assigns "0" to the variable "player", which ultimately makes the human always start the
game. That's still what we want in the Story Mode, remember, so we'll keep the original code as it is, but under it we're going to
insert some code that will check if we're NOT in Story Mode, and if we're not, we'll assign the "player" variable a random value - either
0 for the human player, or 1 for the AI player. So, change the code block above so it looks like this:

Code: Select all

RulesState::RulesState(){
  phase = Constants::MTG_PHASE_FIRSTMAIN;
  player = 0;
 
  // ++ This is where our patch begins ++
  if (GameApp::StoryMode == 0)             // If we're NOT in story mode...
    player = rand() % 2;                   // ... randomize the starting player
  // -- And here our patch ends --
}
STEP 3. [OPTIONAL] IMPLEMENTING THE MANA BURN RULE

This part of the guide implements the optional Mana Burn rule which, per the MTG 2010 rules, was
taken out. If you don't want the mana burn rule, feel free to skip Step 3. Thanks to wololo for correcting
me on how and when it worked! :)


3.1. Open the file GameObserver.cpp (under "Source Files"), and locate a function called
nextGamePhase. It will be defined like this ("..." here and everywhere below means just many lines
of code that I skipped here):

Code: Select all

void GameObserver::nextGamePhase(){
  ...
}
3.2. Inside this function, look for the following block - it's exactly the part that clears the mana
pool at every phase shift:

Code: Select all

  for (int i = 0; i < 2; ++i)
    players[i]->getManaPool()->init();


3.3. Here we'll implement the Mana Burn rule. So, right above that "for" loop, we need to make
some modifications so that the mana burn is applied. Take a look carefully below and insert the lines
between the "++" and the "--" comments:

Code: Select all

  // ++ This is where our patch begins ++
  int manaburn = currentPlayer->getManaPool()->getConvertedCost();  // see how much mana is left
  if (manaburn > 0) {                                               // if there was any,
    JSample * sample = resources.RetrieveSample("manaburn.wav");    // see if a sound manaburn.wav exists,
    if (sample) JSoundSystem::GetInstance()->PlaySample(sample);    // play it if it exists,
    currentPlayer->dealDamage(manaburn);                            // deal damage to the player,
  }
  // -- And here our patch ends --

  // the original "if" block and all the rest of the code follows
  for (int i = 0; i < 2; ++i)
    players[i]->getManaPool()->init();
  ...
3.4. Note that we have referenced a sound file "manaburn.wav" in our patch above - however, such a sound doesn't exist in the original
game. If you want a sound to be played when the player's mana burns, you should put a file named "manaburn.wav" in your
"Res/sound/sfx" folder - either take your favorite mana burn sound, e.g. the Shandalar screech (yeech!), or be creative and invent
something for your mana burn. Note that the mana burn will still work even without that sound file, it just won't be accompanied with a
sound effect.


------------------------------------------------

If you're still with us at this point, congratulations!
Cross your fingers and try compiling and running the game. I hope I didn't miss any steps here, and if I didn't, it should work and you
should now have a random starting player and optionally the mana burn rule implemented. If I did miss something and if it
doesn't work for you, please provide some feedback. :)

If you think of an improvement to my code above, especially the part where I detect the game mode, or if you implement some
more MTG rules, please feel free to share them with the rest of the community here.
Good luck, and happy MTGing!

- Agetian (a.k.a. Gadget2006)
Last edited by Gadget2006 on Sat Jul 03, 2010 9:31 am, edited 6 times in total.
wololo
Site Admin
Posts: 3728
Joined: Wed Oct 15, 2008 12:42 am
Location: Japan

Re: [HOWTO] Rules compliance: Random Starting Player, Mana Burn

Post by wololo »

Thanks for your patch, however I have the following comments. It seems to me you are a bit outdated regarding the rules for mana.
Gadget2006 wrote:Instead, keep
the mana in the mana pool of the player until the end of turn, per the real MTG rules.
That is incorrect, and as far as I know it has never been the case in MTG's rules. Where did you read that?
3. Implement the mana burn rule, whereby all the mana that was still in the mana pool at the end of
player's turn would not just go away, but instead damage the offending player.
Mana burn has been removed from the MTG rules almost 1 year ago, when Magic 2010 was released.

I'm surprised you went through the hassle of correcting the game without making 100% sure you were correct. That being said, you could still have those things become options if they are important to you. Our ultimate goal is flexibility and customization ;)
Gadget2006
Posts: 6
Joined: Tue Jun 22, 2010 5:17 am

Re: [HOWTO] Rules compliance: Random Starting Player, Mana Burn

Post by Gadget2006 »

Hmm, you might be right, I admit I haven't checked with the very latest rule set... And yeah, now that you
mention it, it does seem like the mana was lost at the end of phase instead of at the end of turn, I'll change
things around and will update the guide. Thanks for the comments! :D

EDIT: And yeah, I prefer to keep the mana burn on, so after correcting things around in this guide I'll
still leave the mana burn - in case someone wants it, it might be a good option. :D
Gadget2006
Posts: 6
Joined: Tue Jun 22, 2010 5:17 am

Re: [HOWTO] Rules compliance: Random Starting Player, Mana Burn

Post by Gadget2006 »

UPDATE: I updated the guide according to wololo's comments and suggestions (thanks!)

- The Mana Burn rule is now listed as an optional step (for those who want to play by pre-MTG2010 rules
and for those who like this rule, such as myself, since I think it makes one be more careful :))

- The Mana Burn rule, if implemented, will now be applied at the end of the phase instead of at the end of the
turn. My MTG skills have gotten quite rusty and I forgot how it worked (been away from the game for 2
years, hehe).

The rest of the guide (the random player part) is left unchanged since it did not receive any critical
comments yet. ;)

Thanks again for the tips and corrections! :)
Locked