Spell Book GUI

Started by strangeDetective, Fri 04/11/2016 00:56:00

Previous topic - Next topic

strangeDetective

Very new to scripting and GUIs and I don't even know where to begin with implementing this idea for a GUI. I'm using the very latest version of AGS, I thought there might be a "dialing telephone" module or "door pass-code" module I could experiment with, but the one's I found seem to be over 10 years old.

Posting this on the chance that maybe someone can help.

Basically, the concept is the player would have a "spell book" that could be called up on screen, and on the pages would be 'sigils' which represent 9 word/vowel sounds.

Think of it as a telephone GUI with nine digits (sigils). At the start of the game, only three digits/sigils are available to the player. All the rest are disabled. Additional digits/sigils are enabled to appear in the Spell Book after completing certain tasks during the game.

Player needs to be able to open the Spell Book, select an object/hot spot/character on screen, and then input a sequence of digits/sigils to cast a spell on whatever object or character is currently selected.

This would be very similar to the GUI in LOOM, where you learn and play musical notes to cast certain spells on objects. Only the Spell Book GUI would cause the player to utter incantations, which would have a corresponding effect on an object or character if the correct words/vowels in the Spell Book were spoken in a proper sequence.

A GUI like this might be even more laborious and complex than I can imagine, but as I can't even fathom how to begin building such a thing, I thought I'd take a shot and post.

[If this seems like too enormous a task to ask for help with, I'll move it to the Recruitment section as a (paid) offer to help design. PM me.]



Snarky

Sounds like a cool system! ;-D

It should be pretty easy to create, too. You can create the GUI just as a normal GUI with nine buttons. Each button can be enabled/disabled according to whether the sigil is available: just disable the ones the player doesn't start out with in the editor, and then enable them as they become available. (Depending on how you want it visualized, there might be a little more to it, but you can add the visual pizazz later, once you have it working.)

Now you have to figure out what happens when you click the buttons. A little bit more detail on how it's supposed to work is needed to decide how to implement that:

-Are all the spells a fixed length (e.g. a sequence of four sigils), or do they vary?
-What happens if the user clicks a sigil that isn't part of a valid sequence?
-If the lengths vary, how do you know when a spell is finished?
-Is there any way to cancel and start over?

But basically how I would do it is to create a string that represents the spell, with each sigil corresponding to one character. (With nine of them they can be numerical digits if you like, but it will probably be more readable to use letters.) So every time the user presses a button, you add a character to the string. When the spell is finished, you compare that string to each string in an array of all the valid spells, and if you find a match that means the player has cast the spell. Then for each hotspot/object/character you have a function to handle that action.

Khris

It's not that hard to do. It's just some basic building blocks you need to combine in the proper way.

Let's start with selecting something. To store the selection of an active area, one can use the type together with the id. We also need AGS to not process the click like it usually would, so we need to intercept a left-click. Here's one way:
Code: ags
// the following above on_mouse_click
int locationType, locationID;

void StoreLocation() {
  int mx = mouse.x, my = mouse.y; // make sure we use the same coordinates in all following calls
  locationType = GetLocationType(mx, my);
  if (locationType = eLocationHotspot) {
    Hotspot *h = Hotspot.GetAtScreenXY(mx, my);
    locationID = h.ID;
  }
  if (locationType = eLocationObject) {
    Object *o = Object.GetAtScreenXY(mx, my);
    locationID = o.ID;
  }
  if (locationType = eLocationCharacter) {
    Character *c = Character.GetAtScreenXY(mx, my);
    locationID = c.ID;
  }
  lblSpellTarget.Text = Game.GetLocationName(mx, my); // this will also clear the label
}


Inside on_mouse_click / the eMouseLeft block, we intercept the click if the Spellbook is open:
Code: ags
    if (gSpellbook.Visible) {
      StoreLocation();
      return; // no further action yet
    }


Next we need the Spellbook GUI. We set its visibility to Normal so it doesn't interrupt the game when open.
As soon as the Spellbook is open, left-clicking something will select it and put its description in the label lblSpellTarget. This label can be put on another, persistent GUI, or of course the Spellbook GUI itself.

Next, let's add the first button to the GUI, rename it to btnSigil1 or a more appropriate name and double-click it so AGS will generate the handler function. We will use this one handler function for all nine buttons though.
The function gets added to the bottom of the GlobalScript and will look like this:
Code: ags
function btnSpell1_onClick(GUIControl *control, MouseButton button) {

}

We can use a single function for all nine buttons because the button gets passed into the function (*control), so lets rename the function to Sigils_onClick and paste that name into every sigil button's handler field on its events pane.
Next, let's make sure we only handle a left click though:
Code: ags
// inside Sigils_onClick
  if (button != eMouseLeft) return;

Let's create a spell variable to store the sigils clicked so far. Do so via the Editor's Global Variables pane: type int, default value 0, name "spell".
In the function, we add:
Code: ags
  int sigil = control.ID + 1; // digit 1 - 9 if sigil buttons have IDs 0 - 8
  spell = (spell * 10 + sigil) % 1000; // compose spell by appending digit, spell length = 3
  if (spell > 99 && locationType != eLocationNothing) {
    if (locationType == eLocationHotspot) hotspot[locationID].RunInteraction(eModeUsermode1);
    if (locationType == eLocationObject) object[locationID].RunInteraction(eModeUsermode1);
    if (locationType == eLocationCharacter) character[locationID].RunInteraction(eModeUsermode1);
    spell = 0;
    locationType = eLocationNothing;
  }


To react to the spell I've chosen Usermode1, a custom cursor mode.
Simply add the Usermode1 interaction on the event pane for a hotspot, object or character, and in the function, compare the spell variable to a number like 123.
Code: ags
function hCloset_Mode8()
{
  if (spell == 123) {
    Display("The closet dodges your unlock spell. Some powerful magic is protecting it.");
  }
  else Display("The spell fizzles.");
}

Snarky

#3
Hmmm... Khris has gone with a slightly different approach than I would use.

The stuff for keeping track of the spell target is all good, but personally I would strongly recommend abstracting out the matching of the incantations (sequence of sigils) to particular spells, so you don't have to do a raw comparison in each interaction handler. One of the main reasons is that if you later decide to change what sequence of sigils to use for a spell, you would then have to change it all over the place. It's better to have one method that does it for you.

Here's what I had.

A script module:
Code: ags
// SpellHandler header: SpellHandler.ash

// The different spells in the game; can be whatever you like, except for the first and last entry which are special
enum Spell {
  eSpellInvalid = 0, // special value for invalid spells
  eSpellInvisible,
  eSpellArrow, 
  eSpellFetch, 
  eSpellLevitate, 
  eSpellNuke, // add as many spells as you like after this
  eSPELLCOUNT // this should always be the last entry
};


struct SpellHandler {
  import static void ClickSigil(GUIControl* bSigil,  MouseButton button);
  import static void ClearIncantation();
  import static Spell GetSpell();
};

Code: ags
// SpellHandler script file: SpellHandler.asc

// Just for readability elsewhere, we define the mapping of sigil to character. I just made up some sigils, they can be whatever you want, of course
#define SIGIL_MOON '1'
#define SIGIL_FIRE '2'
#define SIGIL_SPIRAL '3'
#define SIGIL_AIR '4'
#define SIGIL_DEATH '5'
#define SIGIL_WATER '6'
#define SIGIL_STAR '7'
#define SIGIL_EARTH '8'
#define SIGIL_SUN '9'

String incantations[eSPELLCOUNT]; // Here's where we store the incantation strings that match each spell
String incantation; // This is the incantation the player is currently casting

// Here we set up what incantation matches what spell
void SpellHandler_init()
{
  incantation = "";
  incantations[eSpellInvisible] = String.Format("%c%c", SIGIL_MOON, SIGIL_AIR);
  incantations[eSpellArrow]     = String.Format("%c%c%c", SIGIL_SPIRAL, SIGIL_AIR, SIGIL_FIRE);
  incantations[eSpellFetch]     = String.Format("%c%c%c", SIGIL_EARTH, SIGIL_MOON, SIGIL_SPIRAL);
  incantations[eSpellLevitate]  = String.Format("%c%c%c", SIGIL_AIR, SIGIL_WATER, SIGIL_SPIRAL);
  incantations[eSpellNuke]      = String.Format("%c%c%c%c", SIGIL_SUN, SIGIL_DEATH, SIGIL_FIRE, SIGIL_DEATH);
}

// Make sure we initialize the list at game start
function game_start()
{
  SpellHandler_init();
}

// Pressing a sigil simply adds it to the current incantation
static void SpellHandler::ClickSigil(GUIControl* bSigil,  MouseButton button)
{
  if(bSigil == bSigilMoon)
    incantation = incantation.AppendChar(SIGIL_MOON);
  else if(bSigil == bSigilFire)
    incantation = incantation.AppendChar(SIGIL_FIRE);
  else if(bSigil == bSigilSpiral)
    incantation = incantation.AppendChar(SIGIL_SPIRAL);
  else if(bSigil == bSigilAir)
    incantation = incantation.AppendChar(SIGIL_AIR);
  else if(bSigil == bSigilDeath)
    incantation = incantation.AppendChar(SIGIL_DEATH);
  else if(bSigil == bSigilWater)
    incantation = incantation.AppendChar(SIGIL_WATER);
  else if(bSigil == bSigilStar)
    incantation = incantation.AppendChar(SIGIL_STAR);
  else if(bSigil == bSigilEarth)
    incantation = incantation.AppendChar(SIGIL_EARTH);
  else if(bSigil == bSigilSun)
    incantation = incantation.AppendChar(SIGIL_SUN);
}

// Clear the incantation to start over
static void SpellHandler::ClearIncantation()
{
  incantation="";
}

// Match the current incantation to the available spells
static Spell SpellHandler::GetSpell()
{
  int i=1;
  while(i<eSPELLCOUNT)
  {
    if(incantations[i] == incantation)
      return i;
    i++;
  }
  return eSpellInvalid;
}


And a simple incantation GUI (you would of course replace the text on each button with a graphic):


And now we simply make handlers for all the buttons, and map the click events to the handlers:

Code: ags
//----------------------------------------------------------------------------------------------------
// gSpells
//----------------------------------------------------------------------------------------------------

// Use this handler for ALL the sigil buttons
function bSigil_OnClick(GUIControl *control, MouseButton button)
{
  SpellHandler.ClickSigil(control, button);
}

// Use for Clear button
function bClear_OnClick(GUIControl *control, MouseButton button)
{
  SpellHandler.ClearIncantation();
}

// Use for Cast button
function bCast_OnClick(GUIControl *control, MouseButton button)
{
  spell = SpellHandler.GetSpell();  // figure out which spell the incantation matches (if any)
  gSpells.Visible = false;
  // And then Khris's code
  if (locationType != eLocationNothing)
  {
    if (locationType == eLocationHotspot) hotspot[locationID].RunInteraction(eModeUsermode1);
    if (locationType == eLocationObject) object[locationID].RunInteraction(eModeUsermode1);
    if (locationType == eLocationCharacter) character[locationID].RunInteraction(eModeUsermode1);
    spell = 0;
    locationType = eLocationNothing;
  }
}


And in the event handler:
Code: ags
function hCloset_Mode8()
{
  if (spell == eSpellLevitate)
  {
    // Levitate closet
  }
  else Display("The spell fizzles.");
}

strangeDetective

#4
Quote from: Snarky on Fri 04/11/2016 09:04:53
Sounds like a cool system! ;-D

Thank you Snarky and Khris! I'm going to use this concept in two different games I'm making, which have magical-operation systems. One of the things I always liked about LOOM was how the interface encouraged the player to explore the environment, through learning new spells and trying them out on different things to see what happens. I'm making a small demo where you have to learn a magical ritual and perform it correctly to help escape from a dungeon.

The LOOM system was constrained by the limits of technology of its day, so I'm trying to re-work the basic concept here where the player will have to do some preparatory spell-crafting work, to put objects (or characters) into certain states before attempting to invoke something or cast a spell. So that might entail gathering and assembling the proper items in order to perform a ritual, for example, or waiting for a character to fall asleep before attempting to cast a spell on them.

The magic-system in LOOM reminded me a little bit of that old memory game "Simon Says," with the way you had to memorize and input spells as musical notes, I'm almost sure Brian Moriarty was probably inspired by Simon Says when he was designing LOOM:

[1978 Milton Bradley Simon Says game]
https://www.youtube.com/watch?v=vLi1qnRmpe4

So this will be similar but more elaborate than the magic-system used in LOOM. In most magic-systems something horrible happens to the spell caster if they mess up attempting to fool around with the occult, so that might factor into the system in some way.

I'm going to try to start implementing this in a experimental tech demo using the examples you guys gave me, I'll see how far I can get ;) --




Khris

Quote from: Snarky on Fri 04/11/2016 10:45:54I would strongly recommend abstracting out the matching of the incantations
Good point, I didn't think that far ahead.

strangeDetective

#6
Quote from: Snarky on Fri 04/11/2016 10:45:54
Hmmm... Khris has gone with a slightly different approach than I would use.


Hmm...I'm not sure how to combine your and Khris' scripts in order to make it work. I keep getting this error when I try to run:


Does gSpells in your code refer to the GUI of the SpellBook? Or something else? Was assuming it referred to the SpellBook GUI.

Spell Book.asc(104): Error (line 104): Undefined token 'gSpells'

Also, I have got an error which refers to parts of Khris code, but not sure how to merge/mix these two examples together so they work.

Spell Book.asc(106): Error (line 106): undefined symbol 'locationType'

I'm confused. How much of Khris' script should I use when attempting to merge these together. I'm also unsure of where to place these portions of Khris' script in the Global Section.





//----------------------------------------------------------------------------------------------------
    // gSpells
    //----------------------------------------------------------------------------------------------------
     
    // Use this handler for ALL the sigil buttons
    function bSigil_OnClick(GUIControl *control, MouseButton button)
    {
      SpellHandler.ClickSigil(control, button);
    }
     
    // Use for Clear button
    function bClear_OnClick(GUIControl *control, MouseButton button)
    {
      SpellHandler.ClearIncantation();
    }
     
    // Use for Cast button
    function bCast_OnClick(GUIControl *control, MouseButton button)
    {
      spell = SpellHandler.GetSpell();  // figure out which spell the incantation matches (if any)
      //gSpells.Visible = false;
      gSpellBook.Visible = false;
      // And then Khris's code
      if (locationType != eLocationNothing)
      {
        if (locationType == eLocationHotspot) hotspot[locationID].RunInteraction(eModeUsermode2);
        if (locationType == eLocationObject) object[locationID].RunInteraction(eModeUsermode2);
        if (locationType == eLocationCharacter) character[locationID].RunInteraction(eModeUsermode2);
        spell = 0;
        locationType = eLocationNothing;
      }
    }

Snarky

Sorry, I wrote my code independent of Khris, so there are some inconsistencies and things that are a bit tricky to merge.

Yeah, gSpells is the spellbook GUI. It's the same thing that is called gSpellbook in Khris's code.

Most of Khris's code still applies. In fact, I think it's only the "click on sigils button handler" function (which appears in Khris's code first as btnSpell1_onClick() and then renamed to Sigils_onClick(), and in my code as bSigil_OnClick()) that needs to be changed, plus that I have the Clear/Cast buttons and associated handlers (while Khris assumes a constant spell length of three sigils).

Oh, and I don't think either of us actually included the bit where you bring up the spellbook, which should go something like:

Code: ags
  gSpells.Visible = true; // or gSpellBook, or whatever your gui is called
  SpellHandler.ClearIncantation();


That bit of code you quoted is from me, and you can put it anywhere in the global script. It's usually helpful to keep it organized so that all the button handlers for one GUI are together, but it doesn't really matter to AGS.

I also wonder if this is really the best interaction flow. I find it a little weird that you bring up the spellbook first, and THEN choose a target, while also selecting the sigils at the same time. I think it will be confusing particularly if a player enters some sigils, then changes their mind about the target, chooses a different target, and then enters more sigils. Like, do the initial sigils still count, or are they cleared when the target changes?

I think you should either pick a target before you open the spellbook, or complete an incantation first and only then pick a target. Doing it at the same time is just confusing.

strangeDetective

Quote from: Snarky on Fri 04/11/2016 19:34:05
Sorry, I wrote my code independent of Khris, so there are some inconsistencies and things that are a bit tricky to merge.

Yeah, gSpells is the spellbook GUI. It's the same thing that is called gSpellbook in Khris's code.

Most of Khris's code still applies. In fact, I think it's only the "click on sigils button handler" function (which appears in Khris's code first as btnSpell1_onClick() and then renamed to Sigils_onClick(), and in my code as bSigil_OnClick()) that needs to be changed, plus that I have the Clear/Cast buttons and associated handlers (while Khris assumes a constant spell length of three sigils).

Oh, and I don't think either of us actually included the bit where you bring up the spellbook, which should go something like:

Code: ags
  gSpells.Visible = true; // or gSpellBook, or whatever your gui is called
  SpellHandler.ClearIncantation();


That bit of code you quoted is from me, and you can put it anywhere in the global script. It's usually helpful to keep it organized so that all the button handlers for one GUI are together, but it doesn't really matter to AGS.

I also wonder if this is really the best interaction flow. I find it a little weird that you bring up the spellbook first, and THEN choose a target, while also selecting the sigils at the same time. I think it will be confusing particularly if a player enters some sigils, then changes their mind about the target, chooses a different target, and then enters more sigils. Like, do the initial sigils still count, or are they cleared when the target changes?

I think you should either pick a target before you open the spellbook, or complete an incantation first and only then pick a target. Doing it at the same time is just confusing.

Quote from: Snarky on Fri 04/11/2016 19:34:05
Sorry, I wrote my code independent of Khris, so there are some inconsistencies and things that are a bit tricky to merge.

Thanks Snarky, I tried the code Khris wrote but it didn't work, I might have put something in the wrong place. It seemed to run, and sometimes I could select an object to cast a spell on it, but when I entered buttons 1,2,3 nothing would happen.

I'm not sure if your script will work without merging it with portions of Khis's code, I haven't tried it yet because merging the two versions looked more complicated than trying to get one to work independently on its own.

Is there a way to use your code that I can try out, without having to merge it with the other code sections that Khris wrote?

Ideally, I was thinking it would play out this way:

1. The spell book is an icon located on the icon bar that appears when your mouse moves to the top of the screen (Sierra style GUI).

2. Player clicks on Spell Book icon, and the Spell Book GUI appears on screen.

3. Player's cursor mode changes to eUserMode9 (special spell target pointer) and player is prompted to select hotspot/object/character on screen that they wish to cast a spell on.

4. Player selects spell target, which is then reflected on Spell Book GUI as the hotspot/object/character's text label.

5. Player pushes Sigil combo buttons to cast spell on target, then hit's "cast spell" when finished.

[If player messes up entering sigil buttons, they can push "clear" and start over.]

6. Spell succeeds or fails on target, and runs corresponding if/else script. (e.g. Prince turns into frog--or not).

7. After casting spell, player closes Spell Book GUI and normal game interactions return.

I could see both of the scripts emulating this idea, but just haven't figured out how to make them work, or whether there's something missing that's causing it to go wonky.




Snarky

Good approach, trying to get Khris's code to work first, since his solution is more or less complete, while mine is more of an add-on and will not work on its own.

Really, my code pretty much just replaces these lines:

Code: ags
  int sigil = control.ID + 1; // digit 1 - 9 if sigil buttons have IDs 0 - 8
  spell = (spell * 10 + sigil) % 1000; // compose spell by appending digit, spell length = 3
  // ...
  if (spell == 123)


The changes make it so that spells can be any length (adding the Clear/Cast or Start Again/Invoke buttons), and so that you can compare spells by name rather than by checking its sequence of sigils. It's a nice-to-have, but not essential for a version 1.

Maybe just focus on his code for now and figure out where it's breaking for you. You say "nothing happens", but what did you expect to happen (and why did you expect that)?

strangeDetective


I'm stumped, after multiple attempts and do-overs, I'm not sure what I've done wrong in entering Khris's code but I can't seem to work:

Code: ags



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // the following above on_mouse_click
    int locationType, locationID;
     
    void StoreLocation() {
      int mx = mouse.x, my = mouse.y; // make sure we use the same coordinates in all following calls
      locationType = GetLocationType(mx, my);
      if (locationType == eLocationHotspot) {//
        Hotspot *h = Hotspot.GetAtScreenXY(mx, my);
        locationID = h.ID;
      }
      if (locationType == eLocationObject) {
        Object *o = Object.GetAtScreenXY(mx, my);
        locationID = o.ID;
      }
      if (locationType == eLocationCharacter) {
        Character *c = Character.GetAtScreenXY(mx, my);
        locationID = c.ID;
      }
      lblSpellTarget.Text = Game.GetLocationName(mx, my); // this will also clear the label
    }

// SPELL BOOK ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function on_mouse_click(MouseButton button) {
  // called when a mouse button is clicked. button is either LEFT or RIGHT
  if (IsGamePaused() == 1) {
    // Game is paused, so do nothing (ie. don't allow mouse click)
  }
  
  // SPELL BOOK
  
         if (gSpellBook.Visible) {
          StoreLocation();
          return; // no further action yet
        }
  
  else if (button == eMouseLeft) {
    Room.ProcessClick(mouse.x, mouse.y, mouse.Mode );
  }
  else if (button == eMouseRight || button == eMouseWheelSouth){
    // right-click our mouse-wheel down, so cycle cursor
    mouse.SelectNextMode();



Here is the next block of code entered at the end of Global Script:

Code: ags




// SPELL BOOK


function Button4_OnClick(GUIControl *control, MouseButton button)
{
gSpellBook.Visible = true;

}

function Button3_OnClick(GUIControl *control, MouseButton button)
{
gSpellBook.Visible = false;

}

    function Sigils_OnClick(GUIControl *control, MouseButton button) {
     
     if (button != eMouseLeft) return;
     
           int sigil = control.ID + 1; // digit 1 - 9 if sigil buttons have IDs 0 - 8
      spell = (spell * 10 + sigil) % 1000; // compose spell by appending digit, spell length = 3
      if (spell > 99 && locationType != eLocationNothing) {
        if (locationType == eLocationHotspot) hotspot[locationID].RunInteraction(eModeUsermode2);
        if (locationType == eLocationObject) object[locationID].RunInteraction(eModeUsermode2);
        if (locationType == eLocationCharacter) character[locationID].RunInteraction(eModeUsermode2);
        spell = 0;
        locationType = eLocationNothing;
      }
     
     
    }
    
    
    
    



And here's the object (skull) was trying to test it on:

Code: ags



function oSkull_Mode9()
{

    if (spell == 123) {
    cWitch.Say ("The closet dodges your unlock spell. Some powerful magic is protecting it.");
  }
  else Display("The spell fizzles.");




}



When I run the game, I open the SpellBook GUI, click on the skull, but then when I enter 1,2,3 I just get the message "The spell fizzles".

I don't understand why it's not working, or what I've done wrong entering the code...

Snarky

Khris's code relies on the IDs of the GUI buttons. If they weren't created in the expected order, or don't start from 0, it's not going to match what you expect. Try checking what number you're actually entering:

Code: ags
function oSkull_Mode9() {
  if (spell == 123) {
    cWitch.Say ("The closet dodges your unlock spell. Some powerful magic is protecting it.");
  }
  else Display("The spell, %d, fizzles.", spell); // <-- Edited to display the code you entered
}


I should also point out that your indentation is a mess. Try to keep it consistent:

Spoiler
Code: ags
// At first, everything starts off at the beginning of the line
int maxinc = 1000;

// (It doesn't matter what this function does, just focus on the formatting)
function calculateOutput(int iterations, int offset)
{
  // Once you are inside a BLOCK, limited by curly brackets { }, you indent one level (two spaces)
  // You still keep everything aligned, just one level to the right:
  int x=0;
  int i=0;
  while(i>iterations)
  {
    // An opening curly bracket has appeared! We are now inside another block, so we indent another level
    if(i*i > maxinc)
      x = x + maxinc; // If a "block" is only one line long, you can skip the curly brackets, but you still indent it
    else
      x = x + i*i;
    i++;
  } // The closing curly bracket marks the end of the block, and is back out one level of indentation to the left

  // Notice that everything on this block-level is still aligned vertically
  x = x - offset;
  return x;
} // This is the end of the function, and another closing curly bracket, so we're back to the left margin

int transmissionSetting = 5;
int friction = 10;

// Some people put the opening curly bracket at the end of the line above instead of on a line by itself:
bool runMachine() {
  if(calculateOutput(transmissionSetting, friction) > 5000) {
    // TODO: Play machine produces diamond animation
    return true;
  }
  // Finally, when you have an 'if' or an 'else' (etc.) with only a  single short line following it,
  // people will sometimes put it all on a single line. Indent it as the first line (here 'else') would be: 
  else return false; 
}
[close]

strangeDetective

Quote from: Snarky on Sat 05/11/2016 14:42:16
Khris's code relies on the IDs of the GUI buttons. If they weren't created in the expected order, or don't start from 0, it's not going to match what you expect. Try checking what number you're actually entering:

Thanks again, yeah I checked the number and it returns "0" with the 'spell fizzles' message. So looks like only 0 is being counted when I enter 1, 2, 3. All the buttons are mapped from 0-8 and given the same name as example in Khris' code, so not sure why it's not registering the input. Maybe I can try deleting all the buttons and create them again, if I made some kind of spelling mistake creating them and that's not overwritten when you fix it.


Snarky

Hmmm... but it only happens after you press three buttons?

Try removing the "spell = 0;" line in Sigils_OnClick(). I think what's happening is that the RunInteraction() call gets queued until the function finishes, and by that time the spell value has already been reset to 0. If that works, we just need to find a better way to reset the value.

strangeDetective

#14
Quote from: Snarky on Sat 05/11/2016 20:36:46
Hmmm... but it only happens after you press three buttons?

Try removing the "spell = 0;" line in Sigils_OnClick(). I think what's happening is that the RunInteraction() call gets queued until the function finishes, and by that time the spell value has already been reset to 0. If that works, we just need to find a better way to reset the value.

Okay thanks, that makes it take a 3 number input -- but the buttons are all mapped wrong, when I input 1,2,3, I get "spell 345 fizzles"...and similar mixed up variations with different numbers...but as far as I know the buttons are all named correctly, e.g. btnSigil0 = 0 on the SpellBook GUI, that's what I named it when I created it.

My buttons don't have IDs that match 0-8, the ID of Button 0 is ID 1. Is that messing it up?

For example, if I press 0,0,0 it returns 2,2,2...but  don't understand the math logic/code enough to figure out how its being processed.

But it only works once, it will not 'clear' or take any 3 digit input after the first try--I have to quit the game and start it over again.

Snarky

All right, we're getting somewhere!

The button mapping doesn't go by the button name â€" that's just an arbitrary name, and AGS doesn't understand the number part of it. The IDs are a completely separate thing, and are just based on the order in which you create the buttons in the editor. So yes, it's the fact that they don't start from 0 that is messing it up.

You could fix the calculation, but now might be a good time to integrate my code, since it doesn't rely on the button IDs, but uses button names instead. That will also allow you to map the "start over" button, and then you won't have to restart the game each time you've cast a spell. We still need to fix it so it automatically clears spells that are cast, but we can do that later.

strangeDetective

#16
Quote from: Snarky on Sat 05/11/2016 21:41:49
All right, we're getting somewhere!

Great thanks, I'll start on your parts of the code and get it worked in to try out.

Okay it appears to be working after implementing your code, I'll have to play around with it for a bit -- there is one other thing you may know a trick of doing, but after the player hits "cast", before AGS checks to see if the spell succeeds/or fails I wanted to animate the player...I know switching views, etc...but each button will have a sound attached to it (a word/vowel, actually) and I want to animate the player and play back each sound that is assigned to each different button.

So it shows the player repeating the incantation, according to the sequence of buttons they pushed in the spell book. Pretty much same as LOOM. Then AGS checks to see if the spell succeeded/failed on the object.

If you had any thoughts about how to do that would be a great help. Thanks!

Snarky

#17
Yeah, that shouldn't be too hard, at least if the animation is blocking, which I assume it is. Let's just add another method to the SpellHandler module:

Code: ags
static bool SpellHandler::ChantIncantation()
{
  // if(SpellHandler.GetSpell() == eSpellInvalid) return false; // If you uncomment this line, only successful spells will be chanted
  int i=0;
  while(i<incantation.Length)
  {
    if(incantation.Chars[i] == SIGIL_SUN)
    {
       // TODO: Play the audio for this sigil syllable
       // TODO: Play the animation for this sigil gesture, blocking
    }
    else if(incantation.Chars[i] == SIGIL_AIR)
    {
       // TODO: Play the audio for this sigil syllable
       // TODO: Play the animation for this sigil gesture, blocking
    }
    // ... and so on
    i++;
  }
  return (SpellHandler.GetSpell() != eSpellInvalid);
}


You also need to add an import statement to the header; if you compare the other methods, I'm sure you can see how to do that.

And now you just call this method when the player clicks on the "Invoke" button!

strangeDetective

Quote from: Snarky on Sun 06/11/2016 06:59:28
Yeah, that shouldn't be too hard, at least if the animation is blocking, which I assume it is. Let's just add another method to the SpellHandler module:

You also need to add an import statement to the header; if you compare the other methods, I'm sure you can see how to do that.

And now you just call this method when the player clicks on the "Invoke" button!

This is fantastic, thank you again!! I haven't got a chance to try it out yet but am going to try to get to it tonight.

strangeDetective

Quote from: Snarky on Sun 06/11/2016 06:59:28
Yeah, that shouldn't be too hard, at least if the animation is blocking, which I assume it is. Let's just add another method to the SpellHandler module:

Code: ags
static bool SpellHandler::ChantIncantation()
{
  // if(SpellHandler.GetSpell() == eSpellInvalid) return false; // If you uncomment this line, only successful spells will be chanted
  int i=0;
  while(i<incantation.Length)
  {
    if(incantation.Chars[i] == SIGIL_SUN)
    {
       // TODO: Play the audio for this sigil syllable
       // TODO: Play the animation for this sigil gesture, blocking
    }
    else if(incantation.Chars[i] == SIGIL_AIR)
    {
       // TODO: Play the audio for this sigil syllable
       // TODO: Play the animation for this sigil gesture, blocking
    }
    // ... and so on
    i++;
  }
  return (SpellHandler.GetSpell() != eSpellInvalid);
}


You also need to add an import statement to the header; if you compare the other methods, I'm sure you can see how to do that.

And now you just call this method when the player clicks on the "Invoke" button!

I'm just now getting a chance to experiment with this, and I'm not sure how to add the import statement or where exactly it goes. My best guess was this:

    struct SpellHandler {
      import static void ClickSigil(GUIControl* bSigil,  MouseButton button);
      import static void ClearIncantation();
      import static Spell GetSpell();
      import static void ChantIncantation ();
    };

Then I added the code into not the header script section, but the module section of the SpellBook you wrote. When I run the game I get this error which I don't understand:

Spell Book.asc(67): Error (line 67): Size of identifier does not match prototype

Maybe dumb question or I am missing something totally obvious to others, but wondering if there's a specific place in header/module and specific syntax I have to use in order to integrate this last chanting the incantations bit into the SpellBook module?

Snarky

ChantIncantation() returns a bool, so the line in the header should go "import static bool ChantIncantation();"

strangeDetective

Quote from: Snarky on Mon 12/12/2016 15:49:54
ChantIncantation() returns a bool, so the line in the header should go "import static bool ChantIncantation();"

Thanks Snarky I think everything's working with the game's spell casting system now. I'll see if I can break it later during more testing. :)

SMF spam blocked by CleanTalk