Reworking the entire magic system (yay)!

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • TricksterWolf
    Scout
    • Sep 2012
    • 43

    Reworking the entire magic system (yay)!

    This is a thread where I will dump questions I have while I am reworking the entire magic system.

    I have a situation where I want characters to have racial and class-based magic, with potential overlap between the two, and access to more than one realm of magic. The caster armor weight limit and mana stat will be determined by class.

    I'm pulling all the magic information into the "books" themselves and front-loading all of it with a spell parser and a "book" parser instead of saving it with the player's class data. Each "book" will have a realm, and outside of a "book" the language will be generalized to "item", "magical feat", etc. The player will have the caster armor limit, mana stat, lowest-level "spell", total "spells" possible, and list of "spells" they can "cast" in a single struct.

    I don't think it will be hard to calculate and cache the lowest-level "spell" a character can perform or the total number of "spells" a character can have: I should be able to min and sum those at birth.

    It currently looks like there's a hard limit of 98 spells total in the game. Is that because it's using a signed byte? Is 'byte' a signed or unsigned byte? I'd like to pump that sucker up above 255 if possible, so would changing spell_order to a u16b* be sufficient, as I suspect...? Breaking savefiles is (obviously) not a problem.
  • debo
    Veteran
    • Oct 2011
    • 2402

    #2
    Originally posted by TricksterWolf
    This is a thread where I will dump questions I have while I am reworking the entire magic system.

    I have a situation where I want characters to have racial and class-based magic, with potential overlap between the two, and access to more than one realm of magic. The caster armor weight limit and mana stat will be determined by class.

    I'm pulling all the magic information into the "books" themselves and front-loading all of it with a spell parser and a "book" parser instead of saving it with the player's class data. Each "book" will have a realm, and outside of a "book" the language will be generalized to "item", "magical feat", etc. The player will have the caster armor limit, mana stat, lowest-level "spell", total "spells" possible, and list of "spells" they can "cast" in a single struct.

    I don't think it will be hard to calculate and cache the lowest-level "spell" a character can perform or the total number of "spells" a character can have: I should be able to min and sum those at birth.

    It currently looks like there's a hard limit of 98 spells total in the game. Is that because it's using a signed byte? Is 'byte' a signed or unsigned byte? I'd like to pump that sucker up above 255 if possible, so would changing spell_order to a u16b* be sufficient, as I suspect...? Breaking savefiles is (obviously) not a problem.
    Haven't looked at source but usually people #define 'byte' to be 'unsigned char', which tells the compiler to make things basically behave like a bitpattern iirc. You should be able to find it in whatever .h file is the "here's all my shared crap".

    This means that you should have up to 255, unless 'byte' is for some reason 'signed char'. If it's just 'char' I think you're also sort of fine but that type is really only intended to store data that is read via some encoding (e.g. ascii), iirc.

    There doesn't seem much reason in 2015 to not have this be some sort of integer type, so your plan sounds reasonable

    Edit: Even if it's a "signed byte" (a ridiculous concept), you still have up to 127.
    Glaurung, Father of the Dragons says, 'You cannot avoid the ballyhack.'

    Comment

    • Nick
      Vanilla maintainer
      • Apr 2007
      • 9647

      #3
      Originally posted by TricksterWolf
      It currently looks like there's a hard limit of 98 spells total in the game. Is that because it's using a signed byte? Is 'byte' a signed or unsigned byte? I'd like to pump that sucker up above 255 if possible, so would changing spell_order to a u16b* be sufficient, as I suspect...? Breaking savefiles is (obviously) not a problem.
      Why do you think 98 is the limit? I don't think there is a limit in 4.0.

      Interesting ideas, btw
      One for the Dark Lord on his dark throne
      In the Land of Mordor where the Shadows lie.

      Comment

      • TricksterWolf
        Scout
        • Sep 2012
        • 43

        #4
        Originally posted by Nick
        Why do you think 98 is the limit? I don't think there is a limit in 4.0.

        Interesting ideas, btw
        It should be 99 actually.

        The magic number 99 is used as a marker in spell_order* to denote a spell that has never been learned so that it will be skipped when forgetting spells. This implies a player with 100 spells will end up marking a spell as "never learned" in the spell_order, which will cause you to forget only the first 99 spells you learn rather than the most recent spells (among other potentially more serious errors). This is more of an issue for my build because the spell information is no longer going to be stored with the player at all, so I'll want spell_order* to range over all spells in the entire game for simplicity.

        I've fixed this by defining PY_SPELL_NEVER_LEARNED to 65000 (instead of using hard-coded 99 as the cap for spells remembered) and changing it to a u16b. This inflates savefiles by a couple of kilobytes, but means I should be safe for up to 65000 spells.

        And it seems to work so far. The big changes I'm working on now are as follows:

        struct player_magic (loaded from both race.txt and class.txt into player)
        . int spell_first (lv of 1st spell over all spell sources/realms)
        . int spell_weight (by class)
        . int mana_stat (by class; casting stat may be different!)
        . byte *spell_level (req. lv for all spells cast by THIS character, 0 if can't)
        . byte *spell_mana (mana for each spell; 0 is possible)
        . byte *spell_fail (base fail-rate for spells cast by THIS character)
        . byte *spell_flags (moved from player. to player->magic.)
        . u16b *spell_order (moved from player. to player->magic.)

        struct magic_book (loaded from books.txt, probably into obj_kind)
        . int tval
        . int sval
        . int num_spells
        . int *spell_index (must contain at least one spell: spell 0 determines book realm)

        struct magic_spell (loaded from spells.txt into new m_info var)
        . int index
        . char *name
        . int spell_realm
        . char *text
        . struct effect *effect (as before)

        in obj-util.c:
        . struct magic_spell *m_info

        I'm probably adding magic_book to obj_kind (best guestimate), and player_magic is replacing class_magic in player. So it will be player->magic. instead of player->class->magic. essentially, and global m_info will hold all the spell information.

        The big work during birth will be carefully updating the level and fail rates for spells so that the character gets the best level and fail rate from any overlapping items.

        I'm removing sexp entirely in favor of just using (spell_level+1)*(spell_mana+1) to determine exp gain the first time a spell is cast (at all, not per book).

        EDIT: I'm updating this as I change stuff around.
        Last edited by TricksterWolf; September 14, 2015, 16:01.

        Comment

        • PowerWyrm
          Prophet
          • Apr 2008
          • 2987

          #5
          The "99" marker is for illegible spells and that's now obsolete with the refactored spell system. I bet it can be removed easily.
          PWMAngband variant maintainer - check https://github.com/draconisPW/PWMAngband (or http://www.mangband.org/forum/viewforum.php?f=9) to learn more about this new variant!

          Comment

          • TricksterWolf
            Scout
            • Sep 2012
            • 43

            #6
            Originally posted by PowerWyrm
            The "99" marker is for illegible spells and that's now obsolete with the refactored spell system. I bet it can be removed easily.
            Yes, but the problem remains when you hit 256. If my 255th spell is Magic Missile and my 256th spell is Fireball, I'll forget Magic Missile first. Spells may work past 255 but spell-forgetting-order won't because it's stored in a byte. That's what I changed.

            EDIT: Also, I think I want illegible spells now that I'm refactoring the system so spell information is not repeated endlessly in class data. I think the current system of repeating spell information is a mess and it makes class.txt a horrendous pain to read.
            Last edited by TricksterWolf; September 20, 2015, 19:28.

            Comment

            • TricksterWolf
              Scout
              • Sep 2012
              • 43

              #7
              To ask the above less confusingly, perhaps.

              I don't understand how here:

              Code:
              static enum parser_error parse_spell_name(struct parser *p) {
                  struct magic_spell *next = parser_priv(p);
                  struct magic_spell *spell = mem_zalloc(sizeof *spell);
                  spell->name = string_make(parser_getstr(p, "name"));
                  spell->next = next;
                  parser_setpriv(p, spell);
                  return PARSE_ERROR_NONE;
              }
              ...parser_priv(p) successfully gives me a pointer to the next spell yet-to-be-parsed, yet here:

              Code:
              static errr finish_parse_spell(struct parser *p) {
                  m_info = parser_priv(p);
                  parser_destroy(p);
                  return 0;
              }
              ...parser_priv(p) gives me a pointer back to the initial spell after all the spells have been parsed. How can I access the last spell in the list in a way that will let me set its next field to NULL?

              Or more directly, is there some way I can check *p in the very beginning to see if I've parsed the final spell in my spell.txt file...?

              Comment

              • TricksterWolf
                Scout
                • Sep 2012
                • 43

                #8
                Alright, I've decided I'm probably going to use z_info->s_max to keep track of the total spell number, because 0) I can't figure out how to get the parser to hoof me a reference to the final spell parsed without an ugly hack, and 1) it looks like z_info->s_max is totally unused, which is weird.

                Oh, parser. You are so magical.

                EDIT: It's working, though for some reason my spells are loaded into m_info in reverse order.

                I should probably be doing some cleanup, like declaring an array of magic_spell and copying the spell data in and attaching m_info to the array. I don't think it matters, though, because I never use m_info once all of the objects have been parsed (the objects have a separate linked list of magic spells with pointers copied from m_info).
                Last edited by TricksterWolf; September 20, 2015, 19:05.

                Comment

                • TricksterWolf
                  Scout
                  • Sep 2012
                  • 43

                  #9
                  Is there a simple way to flush the debug() buffer? It's usually several hundred characters behind where it should be.

                  Comment

                  Working...
                  😀
                  😂
                  🥰
                  😘
                  🤢
                  😎
                  😞
                  😡
                  👍
                  👎