YASV, need some advice

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Therem Harth
    Knight
    • Jan 2008
    • 926

    YASV, need some advice

    I've started another variant:



    Unfortunately I seem to be in over my head already.

    So: player specialties. Depending on which specialty you pick, you get bonuses when certain things happen. A swordmaster can parry and riposte when attacked; a device specialist will get buffs from using devices; a kickboxer will get melee bonuses for wearing sturdy boots; etc. etc.

    I can see how to implement this. The problem is, I can't see how to implement it in a way that isn't a spectacular mess. I don't want to have checks for different specialties spread throughout the code.

    Is there any way to do this in a neater, more centralized fashion? Preferably without rewriting the whole Angband code base?

    I have some vague (and vaguely similar) ideas, based on my experiences with Neoband - object-like structs, polymorphism, callback functions, etc. But they either require extensive modification to the existing code; lack flexibility; or are just plain ugly.

    Anyone willing to give me some tips? I know O, FA, and ToME 2 all have stuff like this, but that seems to be based on if/else checks spread out everywhere, from what I've seen of their sources.
  • Nick
    Vanilla maintainer
    • Apr 2007
    • 9638

    #2
    Originally posted by Therem Harth
    Is there any way to do this in a neater, more centralized fashion?
    I'm inclined to say no. If you have a swordmaster parrying when attacked, then IMHO the logical place for that to be dealt with is in the attack code. In fact, I have come to the firm view that the code should as much as possible make sense by mimicking the game world. The logic then goes "If a swordmaster is attacked, they get a chance to parry", and the code almost writes itself.

    This is indeed, as you've observed, the way it's done in O/FA, so it's what I'm familiar with. I don't think that's influencing me, though - I think it just represents the fundamental extra complexity you're adding to the game by introducing such specialties.

    I'd also like to say that I think future variants are probably (even at this stage) better based off the current restruct branch, and not just because it gives me more bugfinders
    One for the Dark Lord on his dark throne
    In the Land of Mordor where the Shadows lie.

    Comment

    • Derakon
      Prophet
      • Dec 2009
      • 9022

      #3
      Yeah, I'm with Nick here. If you wanted to be "clean" about it, you'd do so by finding a way to make your custom-behaviors logic more modular. Pyrel's approach to this is to make such behaviors pluggable -- that is, you have a bunch of different functions that can be inserted at various points in the code, and each class (or species of monster, or race, or item, etc.) has various functions they can trigger. Pyrel calls these "procs".

      So for example, your Swordmaster would have a proc (a.k.a. function) attached to them whose trigger condition is "when the character is attacked". When you reach the "character is attacked" point in the code, you iterate over all procs you have that have that trigger condition, and invoke all of them. This would invoke the Swordmaster's ability, which would run his custom code, which would have a chance of nulling the attack and printing a special message.

      I don't know how straightforward this would be to do in C. It's certainly possible, since C has function pointers and that should be all you need, but you're opening yourself up to a lot of tricky bugs because you lose all ability for static type-checking when you use function pointers, and C has lots of ways for dynamic type-checking to fail and create really hard-to-debug errors. I spent a fair amount of time thinking about how to best implement procs (and arguing with various other people on these forums, as I recall ), and I was working in Python, which is a lot more flexible than C.

      Comment

      • AnonymousHero
        Veteran
        • Jun 2007
        • 1393

        #4
        Originally posted by Derakon
        I don't know how straightforward this would be to do in C. It's certainly possible, since C has function pointers and that should be all you need, but you're opening yourself up to a lot of tricky bugs because you lose all ability for static type-checking when you use function pointers, and C has lots of ways for dynamic type-checking to fail and create really hard-to-debug errors. I spent a fair amount of time thinking about how to best implement procs (and arguing with various other people on these forums, as I recall ), and I was working in Python, which is a lot more flexible than C.
        It's certainly possible in C, though, as you say a bit more error prone. This is, in fact, the way extensibility is achieved in Tome 2.x -- it just calls them "hooks". It's somewhat limited in what you can do with though -- if you don't use global variables (which you shouldn't!) then you're pretty limited in what a given hook can actually do. If the person who added the hook didn't include enough in the hook input/output (or the hook is in the wrong place), it's not going to be possible to do it.

        (This is quite similar to Aspect-oriented Programming, but a lot more restricted. Just as with AoP, if you go too far down this road everything becomes spaghetti.)

        Comment

        • Derakon
          Prophet
          • Dec 2009
          • 9022

          #5
          Originally posted by AnonymousHero
          It's certainly possible in C, though, as you say a bit more error prone. This is, in fact, the way extensibility is achieved in Tome 2.x -- it just calls them "hooks". It's somewhat limited in what you can do with though -- if you don't use global variables (which you shouldn't!) then you're pretty limited in what a given hook can actually do. If the person who added the hook didn't include enough in the hook input/output (or the hook is in the wrong place), it's not going to be possible to do it.
          I suspect that to do this in C, you'd have to decide on a function signature for each hook which would provide the necessary context and determine how the return type is evaluated. Of course the compiler won't enforce this for you, but any proc/hook/whatever that doesn't use the signature correctly wouldn't work properly anyway. Better than using global variables!

          (This is quite similar to Aspect-oriented Programming, but a lot more restricted. Just as with AoP, if you go too far down this road everything becomes spaghetti.)
          This is a common problem with any "I need more flexibility" design pattern.

          Comment

          • AnonymousHero
            Veteran
            • Jun 2007
            • 1393

            #6
            Originally posted by Derakon
            I suspect that to do this in C, you'd have to decide on a function signature for each hook which would provide the necessary context and determine how the return type is evaluated. Of course the compiler won't enforce this for you, but any proc/hook/whatever that doesn't use the signature correctly wouldn't work properly anyway. Better than using global variables!
            Exactly. In T2 when I converted all the Lua to C, I just created one struct definition for each hook, so you'd have e.g. give_hook_in/give_hook_out structs and talk_hook_in/talk_hook_out structs, etc. At the point in the C code where the hook was invoked, I'd create the necessary X_hook_in structure and call the general hook mechanism, and casting its result to a X_hook_out structure. It was then up to the caller of "add-this-function-to-X-hook" to make sure that the function would cast its argument to a (pointer to) X_hook_in and return a (pointer to) X_hook_out.

            It's not *that* bad from a type safety perspective, it just requires a little boilerplate/discipline to make sure you always cast appropriately at the start of the hook function. (Perhaps macros could be used to reduce the boilerplate, but I didn't really investigate.)

            My point re: global variables was mostly just that if the provider of a X_hook_in structure didn't give you enough to do what's necessary, you could (in the global-infested T2 code base at least) just access whatever global variables/functions you wanted and hope for the best. Needless to say this leads to an even bigger mess over time.

            Originally posted by Derakon
            This is a common problem with any "I need more flexibility" design pattern.
            Indeed.

            In general, designing everything as independent libraries (rather than a "framework" with hooks) almost always turns out better. IME, at least.

            Comment

            • Therem Harth
              Knight
              • Jan 2008
              • 926

              #7
              Originally posted by AnonymousHero
              In general, designing everything as independent libraries (rather than a "framework" with hooks) almost always turns out better. IME, at least.
              What exactly would this mean, in the context of the Angband code base? Or is it too late for that?

              Comment

              • AnonymousHero
                Veteran
                • Jun 2007
                • 1393

                #8
                Originally posted by Therem Harth
                What exactly would this mean, in the context of the Angband code base? Or is it too late for that?
                Theroetically possible, I suppose, but realistically it's never going to happen. (It would be akin to "restruct"^3 or something like that.)

                The idea would be to fully separate everything from each other into "modules", only relying on as-small-as-possible APIs between modules. For example, all the display-related stuff would be completely separate from the game engine stuff, which would again be separate from the file-parsing, etc. etc.) Obviously there's no such thing as a completely generic APIs, but one could imagine the Angband UI code rewritten into something akin to termbox, etc. etc. The whole engine bit would become an API in its own right and *wouldn't* call directly into UI code, etc.

                Comment

                • MattB
                  Veteran
                  • Mar 2013
                  • 1214

                  #9
                  Pyrel already has this

                  Comment

                  • Nick
                    Vanilla maintainer
                    • Apr 2007
                    • 9638

                    #10
                    Originally posted by AnonymousHero
                    The idea would be to fully separate everything from each other into "modules", only relying on as-small-as-possible APIs between modules. For example, all the display-related stuff would be completely separate from the game engine stuff, which would again be separate from the file-parsing, etc. etc.)
                    That's interesting. This is kind of the spirit of what we're doing with the restructure, without having gone the whole way there. Certainly we've had discussions along the lines of "UI and core need to talk to each other like client and server", and we're trying to separate "functional units" as much as possible.

                    I also should note that I'm happy to get some validation for my naive approach from some real programmers
                    One for the Dark Lord on his dark throne
                    In the Land of Mordor where the Shadows lie.

                    Comment

                    • Derakon
                      Prophet
                      • Dec 2009
                      • 9022

                      #11
                      The general guiding principle for making modular code is that any given bit of code A should only know the bare minimum about how other bits B, C, D operate. Whenever reasonably possible, A should delegate operations that require detailed knowledge to those other components. So for example, if you cast Scare Monster at a monster, the spell effect code could manually examine the target monster's struct for fear resistance, roll the monster's saving throw, and on failure set the monster's AI mode to frightened and print a special message. Or you could say "hey monster, you just got hit by a fear bolt of strength X" and let it do the rest itself. Because really, the spell effect code shouldn't need to know about how fear resistance works or what the specific effects of being frightened are.

                      In practice, it's usually not really feasible to have complete separation at every point in the code, but it's a goal to strive for. The more interdependencies you have in the code, the more "ripple effects" there are -- you change something in A, and suddenly B, C, and D all need to be modified because they have implicit dependencies on the old behavior.

                      Comment

                      • debo
                        Veteran
                        • Oct 2011
                        • 2402

                        #12
                        The secondary definition of "modular code" is "code the way I would have written it, not this other dumb schmuck and that's why I can't find anything it's their fault"
                        Glaurung, Father of the Dragons says, 'You cannot avoid the ballyhack.'

                        Comment

                        • Nick
                          Vanilla maintainer
                          • Apr 2007
                          • 9638

                          #13
                          Originally posted by debo
                          The secondary definition of "modular code" is "code the way I would have written it, not this other dumb schmuck and that's why I can't find anything it's their fault"
                          Cool, someone understands why I wanted to restructure
                          One for the Dark Lord on his dark throne
                          In the Land of Mordor where the Shadows lie.

                          Comment

                          • Derakon
                            Prophet
                            • Dec 2009
                            • 9022

                            #14
                            Originally posted by debo
                            The secondary definition of "modular code" is "code the way I would have written it, not this other dumb schmuck and that's why I can't find anything it's their fault"
                            The worst bit is when you go digging into the commit history to figure out who that dumb schmuck was, and find out it was yourself.

                            Comment

                            • fizzix
                              Prophet
                              • Aug 2009
                              • 3025

                              #15
                              Originally posted by Derakon
                              The worst bit is when you go digging into the commit history to figure out who that dumb schmuck was, and find out it was yourself.
                              I've discovered that the most inept coder in the world, in my opinion, always turns out to fizzix-of-the-past.

                              Comment

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