Pyrel dev log, part 2

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Derakon
    Prophet
    • Dec 2009
    • 9022

    Pyrel dev log, part 2

    In deference to Magnate's insistence on using the threaded forum display, which no longer handles the old thread properly, here's a new dev log. I managed to grab some spare time during my vacation to hammer out new level generation. This includes basic item allocation (scaling appropriately with dungeon level), preservation of persistent objects across levels, and a rudimentary system of rooms and corridors:



    At this point, I think I can plausibly open up Pyrel for other people to hammer on. It is absolutely not bug-free, though I've tried to fix bugs as I find them. It's also not really a "game" yet, so unless you want to work on the code I don't really recommend downloading it. Speaking of downloading, have a download link for a gzipped tarball of the source code.

    Of course, distributing the source via tarball downloads isn't really a good long-term solution. I know most Angband development is on GitHub right now, but any thoughts on using Google Code instead? It provides some nice project-management stuff (bug tracking and a wiki, for example) -- you can see some of what's doable on my old Jetblade project page. Also, I tend to favor Mercurial over Git (mostly because Git's mnemonics for commands make no goddamn sense), though I recognize that most of y'all probably have zero interest in learning another basically-equivalent-to-Git version control system. Making a Google Code project would also require me to select an open-source license to start out (with everything added to the project automatically being stuck under that license). GPL v2 or GPL v3 would seem to be the obvious options; you can see them all here.

    Thoughts?

    I can't guarantee I'll be online much through the next weekend -- vacation and all that.
  • Nick
    Vanilla maintainer
    • Apr 2007
    • 9633

    #2
    Sangband uses Google Code.
    One for the Dark Lord on his dark throne
    In the Land of Mordor where the Shadows lie.

    Comment

    • ekolis
      Knight
      • Apr 2007
      • 921

      #3
      Bitbucket doesn't require you to choose a license...
      You read the scroll labeled NOBIMUS UPSCOTI...
      You are surrounded by a stasis field!
      The tengu tries to teleport, but fails!

      Comment

      • Magnate
        Angband Devteam member
        • May 2007
        • 5110

        #4
        Oh nooo!

        Congrats on releasing it - really glad it got this far.

        Curse you for opening up a project management debate! But I'll use whatever you decide on. It's not like it's crucial that it can share github code with angband anyway!
        "Been away so long I hardly knew the place, gee it's good to be back home" - The Beatles

        Comment

        • Derakon
          Prophet
          • Dec 2009
          • 9022

          #5
          The main concern with GitHub vs. something more centrally controlled is how "interior" project development would be handled. I'm going to consult with some of my more knowledgeable friends about this before making a decision one way or the other.

          In the meantime, I've done some work on creature allocation on new levels. Creatures have a "rarity" field in their records (same as in Angband). The larger the value, the more common the creature. What I'm doing right now is dividing that number by 2 for each level out of depth they are (i.e. monsters native to level 40 are half as common at level 39 as they are at level 40), and subtracting 1 for each level too deep they are (i.e. a monster that gets 100 slots and is native to level 40 gets 100 slots at level 40 and 99 slots at level 41). In other words, exponential decay on the "hard" side, linear decay on the "easy" side. Of course the divisor, and the addend, can both be tweaked for balance purposes.

          I've no idea how Angband is currently handling this, for what it's worth. This seemed simple and was easy to implement though.

          Now, I need to add some filters to the code. Specifically, I need two things:

          * a "never allocate" rule for the Player record.
          * a "only allocate once at a time" rule for creatures with the UNIQUE flag.

          I can see doing this two main ways:

          1) Have special flags for these records (c.f. UNIQUE), check for those flags in the code, and behave differently depending on their presence.

          2) Allow records to have "allocation filter" entries, which refer to functions that must be called when we try to add the corresponding creature to an allocation table. If the function returns false, then the attempt fails.

          The latter case is obviously more flexible, and of course there could be other filters (a drop filter for item allocation is an obvious example). Filters would presumably be implemented somewhat like procs, except that the functions called would have meaningful return values. However, this approach would be rather more work, at least at the outset. Thoughts? Am I missing a third alternative approach?

          Comment

          • Nick
            Vanilla maintainer
            • Apr 2007
            • 9633

            #6
            Originally posted by Derakon
            2) Allow records to have "allocation filter" entries, which refer to functions that must be called when we try to add the corresponding creature to an allocation table. If the function returns false, then the attempt fails.

            The latter case is obviously more flexible, and of course there could be other filters (a drop filter for item allocation is an obvious example). Filters would presumably be implemented somewhat like procs, except that the functions called would have meaningful return values. However, this approach would be rather more work, at least at the outset. Thoughts? Am I missing a third alternative approach?
            I think this is an excellent approach. I already have a hackish version of this in FA, where probabilities are modified or exclusions are made during get_mon_num - but on monster selection from the table rather than before building the table.

            I like your approach much better, and will implement it in Beleriand, where monster allocation was going to need a big rewrite anyway, and eventually in FA.
            One for the Dark Lord on his dark throne
            In the Land of Mordor where the Shadows lie.

            Comment

            • Derakon
              Prophet
              • Dec 2009
              • 9022

              #7
              I should note that one big drawback to the generic filter-function approach is that the creature records may get somewhat repetitively verbose. For example, if UNIQUE is implemented as an allocation filter*, then we'd see this in the record for every unique creature:
              Code:
              {
                  ...
                  "filters": [
                      {
                          "trigger": "onAllocation",
                          "function": "onlyAllocateOnce"
                      }
                  ]
              It seems reasonable that common patterns should be moved from the data files to the main code so that they can be readily referred to by shorthand in the list of creature flags. Hm...or perhaps the list of filters could be polymorphic -- accepting both dictionaries and strings, with the strings assumed to refer to previously-templated functions. Parameterizing those functions would be more difficult, but I expect in many cases (as in this one) there would not be much need.

              * And I just thought of a problem -- Pyrel allocators are currently generate-once, use-multiple things, i.e. a single allocator is made at the start of level generation and is used to make all creatures on the level. This is obviously incompatible with unique monsters, so either I need some trigger to invalidate the current allocator, or I need a second pass to prevent generation of creatures even if they're in the allocator table, which sounds like what Nick was complaining about.

              EDIT: I've made some more changes, not reflected in the tarball:

              * Creatures can (and are) allocated on level creation. Trying to allocate the player will print a harmless error message; "unique" monsters can be multiply allocated.
              * Added display information for all monsters who have special characters in their name. Apparently the code I wrote to convert Angband monster records to Pyrel creature records barfed on those in a rather specific way.
              * Changed the message window so instead of capturing stdout it requires you to call a specific messaging function; thus, stdout can be used for debugging even if the UI is hung.
              * Minor code refactoring and removal of some deadwood.

              The codebase is currently 3338 lines long, of which 787 have comments and 559 are whitespace.
              Last edited by Derakon; July 28, 2012, 05:51.

              Comment

              • other
                Rookie
                • Nov 2010
                • 7

                #8
                Would it be reasonable to add fields to the monster record for "Max_at_once" and "Max_ever"? A Unique would then be a special case with both those at 1, while something like RingWraiths might have both fields at 9, and a monster that avoids others of its kind (Great Wyrms?) might have Max_at_once = 1, but unlimited total population.

                You would then need to keep track of the number of live instances of a given monster, as well as the number killed; the allocation routine would have to check that creating a monster would not violate either limit.

                Comment

                • Derakon
                  Prophet
                  • Dec 2009
                  • 9022

                  #9
                  other: such functionality would be handled as part of the filter definition in the monster record, yes. You could come up with any kind of filter you like, for example that Black Orcs and Uruk-Hai would not show up on the level simultaneously (because of rivalry between Sauron and Saruman, hypothetically-speaking). It'd just have to be coded up.

                  I went ahead and created a project on Bitbucket for Pyrel. Bitbucket is, more or less, Github but for Mercurial instead of Git. It has a similar pull-request functionality, as well as a wiki and issue tracker, so I think it should be all that's needed for now. Please let me know if you encounter issues trying to use it. Of course, that particular project is under my username for now; I should probably sort out how to make an "official" (i.e. unattached to a user) project. But it's not a high priority.

                  And Pyrel is now backed up to someone else's hardware, so there's a bit of peace of mind.

                  Comment

                  • Nick
                    Vanilla maintainer
                    • Apr 2007
                    • 9633

                    #10
                    Actually, having read the post properly, I'm not going to do 2. I'm going to do a kind of a hybrid, I think, where I read flags from the record and then have a filter function (same for all monsters but depending on flags) prior to table allocation. I think approach 2 in C would probably suck. But thanks for making me think about how to improve it.
                    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
                      You could have multiple filter functions even in C, but you'd want a switch statement or something similar to select which one to call. In any event I'm happy to provide a wall to bounce ideas off of.

                      Even in Pyrel I won't be sticking the actual code into the creature record; the creature record will name the function it wants to use to filter its allocation, and that name will be mapped to the actual code to call.

                      I haven't coded much more up yet, since I was hung up on how to properly handle filter functions, especially those dealing with creatures before they get allocated. I wanted to leverage the Proc system I had (since Filters are basically Procs that change game flow instead of modifying game state), but Proc functions are self-contained entities that don't actually exist until their associated Thing is created. Using Procs to control creature allocation would thus require me to instantiate one of every creature every time I build an allocation table, just so I can decide if that creature should be usable. That's no good.

                      I spent some time talking it over with a friend, and he suggested that certain Procs should be "static" (i.e. attached to that class of creatures as a whole, not to a specific one), as determined by their trigger condition (i.e. when they are called). In Pyrel terms this means that the CreatureFactory would have procs associated with it, instead of the Creatures it makes having those Procs. I haven't yet thought of a reason why this couldn't work, so next chance I get I'll be implementing it to see how it flies.

                      Comment

                      • Derakon
                        Prophet
                        • Dec 2009
                        • 9022

                        #12
                        Okay, filter functions are in. I've also allowed Procs in general to attach to Creatures, which in hindsight is an obvious requirement for e.g. letting them have unusual effects on their melee hits. Heck, maybe even spellcasting will be handled via Procs; we'll see. More likely AI spellcasting will use the same system I have planned for player spellcasting -- selecting a spell from a container (spellbook). Which implies that AI creatures will be able to use at least some items from their inventories. Ho hum.

                        Also, I whipped together a little script to generate some basic stats on what the codebase looks like.



                        This has all been pushed to the Bitbucket repo, though I've a feeling nobody's tried to use that yet besides me. Oh well; it's still coming in handy even so.

                        Comment

                        • Derakon
                          Prophet
                          • Dec 2009
                          • 9022

                          #13
                          Okay, time to talk out some more design logic. I decided I wanted to implement the 'l'ook command, and this brought me to the realization that my current "prompt the user for a selection" system is badly-designed. The way this currently works is like follows:

                          1) Game is waiting for the next command.
                          2) User presses a key.
                          3) This is propagated to all Listeners, which then map it to some code to execute, in a gigantic block of if/else code. Some of these bits may return Prompts. Prompts are container classes (almost no associated code) that include a prompt type (e.g. yes/no, list items, select direction), a possible Container of Things associated with the Prompt, a message, and a few other doodads.
                          4) Returned Prompts are handed to the code that processes input and the code that handles game display.
                          5) All code dealing with processing and displaying Prompts (and layering, in the event that conclusion of one Prompt requires another Prompt) is external to the Prompts.

                          Displaying actually is reasonably sensible -- different display layers may want to handle displaying a selection of items differently, for example. The reason I separated out the input handling logic as well was that I was thinking about alternate input modes. For example, an item list might receive a selection via the mouse -- how would the Prompt know how to handle that if it doesn't know how it's being displayed? But I think the right answer here is that Prompts can handle keyboard input on their own just fine -- and I'll deal with mouse input later. More specifically, eventually Pyrel will need a mouse handler, and that handler will of course need to know about what is currently displayed. Mouse events will have to be translated by the handler into conceptual actions, like "select item from container" and "set target to this tile", which will need appropriate functions written for them -- but they're ultimately just more kinds of input, they just have to be handled slightly differently.

                          In practice, what we want to be doing here is have Prompts occupy a conceptual "layer" that sits on top of the game world. As long as a Prompt is active, it receives all user input (instead of that input going to any Listeners); meanwhile, Prompts are assumed to be drawn on top of everything else, though that can be left up to the display code. So. Here's my proposed new design:

                          1) Make the Prompt class heirarchical -- each Prompt contains beneath it the Prompt that its answer will assist. For example, if you want to use an item, you get a SelectItemPrompt. You select a spellbook -> the SelectItemPrompt creates another SelectItemPrompt beneath it to let you choose which spell. You select a spell -> the second Prompt creates a SelectTargetPrompt to decide where the spell goes. At any point we can unwind the stack to back out from one of these Prompts.

                          2) Add a "curPrompt" field to the GameMap. Currently Prompts are stored in the actual GUI window instance (that is, a wxWidgets Frame class), which is frankly pretty stupid and makes it unnecessarily difficult for the inevitable switch to Qt once someone who feels strongly about it gets interested in the project. From the GameMap, they're accessible to all input-handling and display-handling code, so input can be redirected as needed and display can show them on top of the normal stuff.

                          3) Modify GameMap.onCommand to check for a valid Prompt; if there is one, it gets input instead of the Listeners.

                          4) When a new Prompt is created, the GameMap is told to set it as the current Prompt. Similarly, when a Prompt is unwound (i.e. cancelled), it sets its parent as the current Prompt -- this may be None if it was the top-level Prompt, in which case input reverts to going to the Listeners.

                          5) Shunt all the current Prompt-handling code from gui.mainFrame to various subclasses of Prompt, replacing the current "prompt type" field in the base Prompt class. Looks like we'll need YesNoPrompt, ItemListPrompt (select item from container), ItemMapPrompt (used for displaying equipment, where each item gets a special label), FormattedMessagePrompt (for stuff like monster memory or detailed item info), DirectionPrompt (tunneling, disarming, etc.), and TargetPrompt (looking, targeting) to cover the ones that currently exist.

                          6) Since I'll be vaguely in the area anyway, replace the giant if/else statement in Listener that maps commands to results of commands with a dictionary that maps commands to functions that handle those commands. Functionally similar but more elegant and probably slightly more efficient.

                          Comment

                          • Derakon
                            Prophet
                            • Dec 2009
                            • 9022

                            #14
                            I'm still working on the Prompt rewrite, and some discussion with some friends of mine yesterday convinced me I wasn't quite on the right track even with the new system...so it''s gonna be a bit before this is done.

                            In the meantime, I had an idea for tweaking how Hallucination works. Right now it'd just be a display layer hack, where each tile would have a small random chance of being replaced by a random tile. Instead, I thought it'd be neat to occasionally spawn hallucinatory monsters and items (in the same manner that normal monsters and items are spawned). These things would last until hallucination ran out or you tried to interact with them; they'd be spawned as if they were native to a significantly deeper depth than usual; most importantly, hallucination itself would be an invisible ailment (you wouldn't be told that you were hallucinating) with a long timeout. Of course all the monsters could do is move -- no melee or ranged abilities.

                            You could also double up on pack monsters that already exist, mixing in hallucinatory ones with the real ones. Fortunately, Pyrel is already capable of handling multiple monsters on the same tile, so other monsters wouldn't have to worry about the fact that the hallucinatory ones don't actually exist.

                            So eating a Mushroom of Emergency, say, would work better in the short term, but you'd spend a looooong time wondering if those nasty out-of-depth monsters you detected around the corner are real or not, or if it's really worth chasing after that unidentified ring you spotted in a vault.

                            Comment

                            • buzzkill
                              Prophet
                              • May 2008
                              • 2939

                              #15
                              Originally posted by Derakon
                              most importantly, hallucination itself would be an invisible ailment (you wouldn't be told that you were hallucinating) with a long timeout.
                              This piece of the puzzle is brilliant. FYI, DaJ already re-did hallucination with some concepts similar to your own, might be worth peeking into.
                              www.mediafire.com/buzzkill - Get your 32x32 tiles here. UT32 now compatible Ironband and Quickband 9/6/2012.
                              My banding life on Buzzkill's ladder.

                              Comment

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