Coding a Data-Driven Roguelike (Again)

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Thu Feb 02, 2017 3:15 pm UTC

So, I'm still in turnaround at work (which means I'm still doing back to back 12 hour shifts -- and have been, for... two months, now, I think?); that being said, things are starting to quiet down a little, which means I have some actual time to myself now and then. On one hand, this is a great opportunity to actually start working on the data-entry app that others have helped set up for me (thank you, Zamfir -- and everyone else who replied!); on the other hand, I'm feeling kind of burnt out on work-related stuff right now, and need an escape. I'm definitely going to come back to that data-entry app -- but probably not until turnaround is completely finished.

I've been working on a data-driven Roguelike that uses a flexible, modifiable D20 ruleset for a long time, now. I've come back to it over the past few weeks and refactored some of my code. I like where I am, but I'd also like feedback on my design:

All dice rolls are treated as limited events; this event has one or more subjects (entities related to the event). When the event is initialized, all of its subjects get a chance to modify the event's internal values before the instance is 'returned'. These modifications effectively serve as all the 'special rules' that make up the game -- things like class abilities, enchantments, etc.

Here's a simple example: In 5th edition, rogues get a special class ability called 'Expertise'. This lets them select one skill they're proficient in; they may now add their proficiency bonus twice to this skill whenever they roll a check for it.

The player picks 'Stealth'. Therefore, I add the following to the player's behavior:

Code: Select all

def add_proficiency_bonus(check):
   check.bonus += check.subjects["agent"].score.proficieny_bonus

player.behavior["StealthCheck"]["agent"].append(add_proficiency_bonus)


(This isn't perfect just yet; I also need a way to be able to remove these special rules -- so 'behavior' will probably end up as a specialized object rather than just a dictionary of dictionaries)

More broadly, let's say you have a spell that grants you +1 to all skill checks. Well, StealthCheck is a child of SkillCheck (which is a child of Check), so:

Code: Select all

def add_plus_one(check):
    check.bonus += 1

player.behavior["SkillCheck"]["agent"].append(add_plus_one)


The next step I'm working on now is how to create abilities and actions using these check objects (and how to integrate damage dice, which are similar to Checks, but also fundamentally different)

KnightExemplar
Posts: 5492
Joined: Sun Dec 26, 2010 1:58 pm UTC

Re: Coding a Data-Driven Roguelike (Again)

Postby KnightExemplar » Thu Feb 02, 2017 5:42 pm UTC

Let me offer you some study material.

https://github.com/Zarel/Pokemon-Showdo ... a/moves.js

Your "events" will be different than Pokémon's of course. But a similar methodology would be beneficial.

Code: Select all

   "acupressure": {
      num: 367,
      accuracy: true,
      basePower: 0,
      category: "Status",
      desc: "Raises a random stat by 2 stages as long as the stat is not already at stage 6. The user can choose to use this move on itself or an adjacent ally. Fails if no stat stage can be raised or if used on an ally with a substitute.",
      shortDesc: "Raises a random stat of the user or an ally by 2.",
      id: "acupressure",
      name: "Acupressure",
      pp: 30,
      priority: 0,
      flags: {},
      onHit: function (target) {
         let stats = [];
         for (let stat in target.boosts) {
            if (target.boosts[stat] < 6) {
               stats.push(stat);
            }
         }
         if (stats.length) {
            let randomStat = stats[this.random(stats.length)];
            let boost = {};
            boost[randomStat] = 2;
            this.boost(boost);
         } else {
            return false;
         }
      },
      secondary: false,
      target: "adjacentAllyOrSelf",
      type: "Normal",
      zMoveEffect: 'crit2',
      contestType: "Tough",
},


It should be mentioned that "Acupressure" is an attack in Pokémon that randomly boosts a statistic. This is the only attack that does something like this, which is why its a great example of the PokemonShowdown engine design. You've got the lambda function for customized attacks.

There are also a whole slew of "standard" attacks that require no additional code... and only change in some situations. Like PP or Accuracy. So you can have a grossly efficient method of inputting your "game logic" as data.

Well, StealthCheck is a child of SkillCheck (which is a child of Check), so:


I dunno if "StealthCheck" would be a good class. A more generalized SkillCheck with lambdas probably would be easier to code and maintain in the long run.
First Strike +1/+1 and Indestructible.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Thu Feb 02, 2017 6:18 pm UTC

Oh; thanks! I didn't think of using lambdas for this (also, I'll read through the Pokemon showdown engine to see if I can lift some insights from it).
KnightExemplar wrote:I dunno if "StealthCheck" would be a good class. A more generalized SkillCheck with lambdas probably would be easier to code and maintain in the long run.
Possibly, but I'm using Python's class inheritance structure to handle event-triggers. In other words, player.behavior["SkillCheck"] would trigger on occurrences of StealthCheck (because StealthCheck is a child of SkillCheck). player.behavior["Check"] would trigger on any occurrence that's a check (Stealth or otherwise); player.behavior["Roll"] would apply to anything that's a roll (Checks or otherwise).

Having that sort of inheritance structure is important, since D&D often has situations that apply the general to the specific (such as a +1 to all 'skill checks').

KnightExemplar
Posts: 5492
Joined: Sun Dec 26, 2010 1:58 pm UTC

Re: Coding a Data-Driven Roguelike (Again)

Postby KnightExemplar » Thu Feb 02, 2017 7:02 pm UTC

The main issue is that data-driven -> object oriented are two fundamentally different models.

The deeper your class hierarchy, the more code you'll have to write to "translate" your data-driven configuration data into object oriented classes. Code that will rely upon the class hierarchy. ex: Your loader code, which reads the data and then converts all of the data into objects that can be handled by the game engine. It will be filled with code like "if data.onDiceRoll == "MagicString": create StealthCheckObject.". And other code that says "CreateAcrobaticsCheckObject".

So you lose the flexibility of the class hierarchy... whatever classes you do make... you won't really be able to change in practice due to the weight of the supporting "translation" code. Which is fine... but it means that you should think very carefully about what classes you're making and make sure you only make classes when absolutely necessary.

The fewer classes and the shallower your inheritance tree, the better (at least when working with "Data Driven"). Its impossible to get through this without some degree of "translation code" between the two styles. In many cases, you really just want:

SkillCheck.skillToCheck = "Stealth"

Instead of:

SkillCheck = new StealthCheck();

The first code is quite easy in practice. You validate to make sure the string is one of the valid strings, then you just do a simple parameter = string to cover all the cases. The second code is very difficult, since you need to be choosing the correct class for all of the configured strings. (String "StealthCheck" creates StealthCheck object. String("AcrobaticsCheck") creates AcrobaticsCheck object). I know in Python you can use reflection to create the right class... but... I'd rather not use reflection if there is a easier approach.

Like... just passing strings around everywhere. Strings do get messy though.

Having that sort of inheritance structure is important, since D&D often has situations that apply the general to the specific (such as a +1 to all 'skill checks').


The functional programming way would be to define a function for each of those situations. (Ex: allSkillCheckFunction = (lambda stuff)).

I personally find it easier to do that then writing a class for every case.
First Strike +1/+1 and Indestructible.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Thu Feb 02, 2017 9:08 pm UTC

Fair enough; that being said -- I don't know if this matters, but the way I've set this up, defining each check class for a skill is (literally) one line of code:

Code: Select all

StealthCheck = new_check("StealthCheck")
AcrobaticsCheck = new_check("AcrobaticsCheck")
...which I can thereafter create hooks for via strings.

Code: Select all

player.behavior["StealthCheck"]
Things do get 'trickier' when I'm using class inheritance to associate certain checks with a group, though:

Code: Select all

AttackCheck = new_check("AttackCheck")
MeleeAttackCheck = new_check("MeleeAttackCheck", AttackCheck)
RangedAttackCheck = new_check("RangedAttackCheck", AttackCheck)
...since I need to pass the inheriting class over to the new classes (so it can function as their base).

The flexibility of inheritance is important for the purposes of allowing certain rules to have appropriate 'hooks' (there needs to be a way to allow a spell that gives you +1 to all skill-checks to identify stealth as a skill, for example). That being said, I don't necessarily need classes to have inheritance; particularly not if I'm avoiding multiple inheritance (stuff like inheriting from two classes). I just need a simple unordered tree.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Sat Feb 04, 2017 11:39 am UTC

I kind of rethought my approach after dwelling on a lot of things. Instead of defining checks as events, I think I'm going to define attributes (Strength, Strength Bonus, Armor Class) as data (in a yaml file):

Code: Select all

Strength:
  Attribute: strength
  Check: True
  Description: Strength measures your character's muscle and physical power.
  Group: Abilities

Stealth:
  Attribute: stealth
  Check: True
  Description: Stealth measures your skill at hiding from sight.
  Group: Skills

Melee:
  Attribute: melee
  Check: True
  Description: This value is used to determine your overall aptitude at striking a target in melee combat.

Melee Bonus:
  Attribute: melee_bonus
  Check: False
  Description: This value represents the overall suitability for an object to be used in melee combat. Masterwork weapons may impart a +1 bonus, for example; a chair -- or person -- not suited as a melee weapon might impart a -1 penalty.
The code would create checks for whatever attributes you define as having a check, then.

KnightExemplar
Posts: 5492
Joined: Sun Dec 26, 2010 1:58 pm UTC

Re: Coding a Data-Driven Roguelike (Again)

Postby KnightExemplar » Tue Feb 07, 2017 1:53 am UTC

The Great Hippo wrote:I kind of rethought my approach after dwelling on a lot of things. Instead of defining checks as events, I think I'm going to define attributes (Strength, Strength Bonus, Armor Class) as data (in a yaml file):


Do you plan to change attributes?

Never program flexibility unless you plan to use it. (or even plan to maybe use it). IMO, it seems very unlikely that attributes would be added over time, so it seems like you'd be building flexibility where it isn't needed.
First Strike +1/+1 and Indestructible.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Tue Feb 07, 2017 11:21 am UTC

I might change them around a little bit, but probably not too much; my intention was to make it possible to code 3.5 and 5th with the same basic engine, which means some things (fortitude/reflex/will; certain skills) would come and go.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Sat Feb 18, 2017 11:21 am UTC

I've created a pretty useful generalized solution for handling checks; the main problem I've been struggling with is how to use this same system to also handle damage. Damage rolls are unique in D&D, in that they're really... uh, weird -- compared to everything else. For starters, they're non-binary (you don't 'fail' or 'succeed').

I've been trying to figure out a way to turn damage rolls into check rolls without relying on things like 'degrees' of success. Then I remembered SecondTalon describing a damage system to me a few years ago in another thread:
SecondTalon wrote:You start the game with 20 hit points, so your Save vs Being Injured is at +20. An attack reduces your save by 5 and then makes you save vs. Injury. If you fail, you roll on the Injury table and.. start over. You're back to 20 hit points, but you have a twisted leg (half movement) or whatever. Repeat. Next failure breaks your leg. Next failure severs it, maybe. I dunno, go nuts.

The idea here is that you could potentially survive some pretty serious damage and be A-OK. And then again the first hit might damn near rip your arm off.
I actually think this system would integrate pretty well into my code. There's a lot of things I like about it:

  • It reduces damage values to integers rather than ranges (a warhammer could do 10 damage, rather than 1d10).
  • It creates a separation between 'hitpoints' (effectively your resistance to harm) and 'injuries' (long-term damage you've suffered).
  • It handles 'degrees' of injuries via a simple saving throw mechanic (every time you fail, the injury becomes one degree worse, and you need to roll again).
  • It allows for weapons that have no damage value (only inflicting injuries if your hitpoints are too low to make a save).

I'm struggling a little with the reset mechanic, though. I'm also trying to think of some ways to make this more streamlined and easier to digest.

EDIT: So, like, I'm attacking you with my warhammer. I roll to hit; I succeed. When you get hit, you need to roll a save against damage (1d20 + resilience; DC = 10 + damage). You have a 'Resilience' of 3; my warhammer does 5 damage. So it's 1d20+3 to beat a DC of 15.

You roll 6. That's 6+3; 9. You fail your save, granting you a minor injury. Now you need to roll again -- you roll a 12. That's 11+3; 14. You fail again! It's now a moderate injury. Roll again; 12. That's 12+3; 15. You succeed, walking away with a moderate injury (which grants you -2 on all future rolls until it's addressed).

(Injuries and their penalties are cumulative; six minor injuries means -6 to all your rolls, including your future saves against damage. Also, damage is neither accumulated nor recorded; there are no hitpoints under this system)

That feels more streamlined and simplified -- but it doesn't address how massive damage can take you down pretty fast. You've still got a 5% chance to roll a 20, but any attack that's doing, like, 15 more damage than your 'resilience' score (or whatever score you use for the save) is probably just going to flat-out kill you.

EDIT-EDIT: Oh! Though maybe not, because -- the only way to flat-out die is to receive a mortal injury, and each tier of injury between minor and mortal is another chance to succeed on your roll (and get that 5%).

So, if there are four tiers before 'mortal', that's four chances to roll a 20; if that's not enough, I can just add another tier to make the game more survivable (and if it's too many, I can just take one tier out). I think this should work? I think I can even integrate armor into it (adding a bonus to your resilience score). Vulnerabilities and resistances just double/half the damage value (my warhammer of fiery doom does 4 blunt and 6 fire; when I hit a water elemental with it, it's treated as a 16 damage weapon -- when I hit a fire elemental with it, it's treated as a 7 damage weapon).

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Mon Feb 27, 2017 7:36 pm UTC

I made a weird amount of progress over the weekend, particularly considering that my computer up and died on me.

I've gotten to the part where I'm building a graphical interface with kivy; right now, I'm trying to figure out how I want to store information on geography (where a solid block is, where an empty space is, where a wall is).

I'm using an entity-component-system, which means everything is an entity with associated components (and those components effectively define the entity's behavior). For this, I'm thinking that dungeons are entities with a 'geography' component that consists of a dictionary of tuples (x-y-z) associated with blocks (which are just another type of entity).

User avatar
Quizatzhaderac
Posts: 1510
Joined: Sun Oct 19, 2008 5:28 pm UTC
Location: Space Florida

Re: Coding a Data-Driven Roguelike (Again)

Postby Quizatzhaderac » Tue Feb 28, 2017 10:49 pm UTC

The Great Hippo wrote:So, if there are four tiers before 'mortal', that's four chances to roll a 20; if that's not enough, I can just add another tier to make the game more survivable
Two recommendations:

One: a bonus on each successive roll to prevent going through too many categories at once. Also, potentially make the bonus apply to the natural save, so on the second roll an natural 19 saves, and a natural 18 on the third.

Two: Two sets of tiers: Valid and invalid tiers. So let's say a system with four tiers in each set: Perfect, off-balance, compromised, maimed, incapacitated, unconscious, comic-book-dead, Shakespeare-dead. If everyone is incapacitated in combat with monsters then TPK. If someone's comic-book-dead, then maybe they need a special item or spell to recover. Shakespeare-dead and you burn the character sheet.
The thing about recursion problems is that they tend to contain other recursion problems.

User avatar
The Great Hippo
Swans ARE SHARP
Posts: 6874
Joined: Fri Dec 14, 2007 4:43 am UTC
Location: behind you

Re: Coding a Data-Driven Roguelike (Again)

Postby The Great Hippo » Wed Mar 01, 2017 5:29 am UTC

Quizatzhaderac wrote:One: a bonus on each successive roll to prevent going through too many categories at once. Also, potentially make the bonus apply to the natural save, so on the second roll an natural 19 saves, and a natural 18 on the third.
Interesting! You just increase the crit-range on each successive roll by one, then.
Quizatzhaderac wrote:Two: Two sets of tiers: Valid and invalid tiers. So let's say a system with four tiers in each set: Perfect, off-balance, compromised, maimed, incapacitated, unconscious, comic-book-dead, Shakespeare-dead. If everyone is incapacitated in combat with monsters then TPK. If someone's comic-book-dead, then maybe they need a special item or spell to recover. Shakespeare-dead and you burn the character sheet.
I like the idea of 'shakespeare-dead' versus 'comic-book-dead'.

I actually ended up going back to the typical D20 hitpoint system after refactoring my code and realizing a simple way to integrate it; that being said, I've left a hook open to make damage based on d20 rolls, instead.

I'm currently tinkering with unittests for my event-component-system, making sure it operates as-intended. Integrating it with kivy turned out to be easier than I thought. I'm a little worried that it will ultimately end up running slow, but I'll try to concern myself with that once it's actually in a playable state.

The next part I want to mess with is touching back on my super-old random-dungeon-generation code. One of the ideas I've wanted to play with for a long while involves 'organic' dungeons -- growing a dungeon rather than just generating it. That is to say, generate a dungeon based on a randomly generated history for it.

You start with a structure (let's say a crypt); once generated, you start moving time forward in increments (10 years?) and generating events that modify that structure. Maybe a necromancer moves in; okay, now the crypt is full of undead. Maybe the necromancer hires kobolds; now the crypt has kobold warrens. Adventurers swoop in and kill the necromancer; the kobolds dig too deep and awaken an ancient demon that enslaves them.

So you end up with a crypt with a bunch of leaderless undead, maybe a few adventurer corpses, and a troupe of kobolds far below being ruled by a demon -- as one example.

(This might be far too ambitious of me, but it's been an angle I've always wanted to try. To start, I might just stick with my old room-and-tunnel generators, though.)

KnightExemplar
Posts: 5492
Joined: Sun Dec 26, 2010 1:58 pm UTC

Re: Coding a Data-Driven Roguelike (Again)

Postby KnightExemplar » Wed Mar 01, 2017 7:53 am UTC

Quizatzhaderac wrote:Two: Two sets of tiers: Valid and invalid tiers. So let's say a system with four tiers in each set: Perfect, off-balance, compromised, maimed, incapacitated, unconscious, comic-book-dead, Shakespeare-dead. If everyone is incapacitated in combat with monsters then TPK. If someone's comic-book-dead, then maybe they need a special item or spell to recover. Shakespeare-dead and you burn the character sheet.


The Pathfinder system of "deadness" is as follows:

* Alive
* Exactly 0-HP (Staggered: can only take "movement" actions without penalty. A "standard" action causes you to go -1 HP)
* Negative HP: Bleeding out and unconscious. Circumvented with any healing spell.
* "Dead" -- Standard Death. Can be circumvented with "Raise Dead" (Level 5 spell)
* "Dead with Body desecrated / Killed with death effect" -- "Raise Dead" does not function. Must use "Resurrection" or higher level spells. "Desecration" includes being turned into an Undead (vampire or whatnot) and then "rekilled". Or incinerated. Or the bad-guys decided to chop up your body into little pieces.
* "Dead with part of body found -- "Resurrection" (Level 7) and "Wish" will function. But a piece of the body must be found for it to work. The ashes of a slain person still count as "part of body found".
* "Dead with body destroyed" -- Must use "Wish" (Level 9) to get the body back (followed by another "Wish" or "Resurrection" to resurrect). Can use "True Resurrection" (Level 9) to recreate the body and resurrect at the same time.
* "Soul trapped" -- Must destroy the prison where the soul is bound before True Resurrection functions. Wish can be used to recreate the body, but the soul will not be bound to the body until the prison is destroyed.

In essence: "Soul trapped" requires a quest AND a max-out level 9 spell to circumvent.
First Strike +1/+1 and Indestructible.

User avatar
Quizatzhaderac
Posts: 1510
Joined: Sun Oct 19, 2008 5:28 pm UTC
Location: Space Florida

Re: Coding a Data-Driven Roguelike (Again)

Postby Quizatzhaderac » Wed Mar 01, 2017 4:47 pm UTC

I've never played a real D&D game, but the zero and negative HP states seem too narrow to me. I'd imagine zero hp is rare unless the attacker is using non-lethal damage.

As for negative HP, I believe it only goes down to -10? I would guess at high levels there's a high risk of blowing straight through that.

I also take issue with (not the game mechanics) but the use of the word "dead" in the various levels of dead. I believe "dead" should be the most serious thing the setting allows. It undercuts the drama every situation where one has to ask "a little dead or a lot dead?" or one can say "He's dead and can't come to phone right now, but if you'll leave a message I'll see he gets it after we raise him".
Last edited by Quizatzhaderac on Tue Jun 13, 2017 2:46 pm UTC, edited 2 times in total.
The thing about recursion problems is that they tend to contain other recursion problems.

KnightExemplar
Posts: 5492
Joined: Sun Dec 26, 2010 1:58 pm UTC

Re: Coding a Data-Driven Roguelike (Again)

Postby KnightExemplar » Wed Mar 01, 2017 5:06 pm UTC

Quizatzhaderac wrote:I've never played a real D7D game, but the zero and negative HP states seem to narrow to me. I'd imagine zero hp is rare unless the attacker is using non-lethal damage.


Rare, but amusing. I like the mechanic personally, since it literally means "resting on the razor's edge" between consciousness and unconsciousness.

As for negative HP, I believe it only goes down to -10? I would guess at high levels there's a high risk of blowing straight through that.


Not quite -10, but close to it. (its negative CON to be precise). At high levels, you blow through it... but usually that's not a problem at high levels because people can be "Raise Dead" by those levels.

I also take issue with (not the game mechanics) but the use of the word "dead" in the various levels of dead. I believe "dead" should be the most serious thing the setting allows. It undercuts the drama every situation where one has to ask "a little dead or a lot dead?" or one can say "He's dead and can't come to phone right now, but if you'll leave a message I'll see he gets it after we raise him".


Consider that Gandalf is no more powerful than a ~level 7 Wizard (access to level 4 spells at the best) and that Legolas is no better than a ~level 5 Ranger (Can shoot two arrows at once... approximately 5 arrows at 4 different targets in 6 seconds). Pathfinder scales into the epic. Standard Death is literally no longer an issue for ~level 10 characters, while level ~17 characters get the almighty reality-warping "Wish" spell. By level 20, characters should be slaying gods and permanently affecting the history of the setting.

Case in point: the heralds of the gods (literally the "right hand angel") are level 15. The pantheon gods don't have levels, but Lords of Hell are ~24 or so... and lesser gods are ~22 to 25.

For players who dislike resurrection mechanics, the solution is simple. End your campaign before level 9. The entirety of "Lord of the Rings" would barely scratch above level 7 (and mind you: Gandalf got rez'd)

-----------

In any case, when your players are godslaying (or demon-slaying) heroes changing the fate of the freaking Pantheon... yeah... Death kind of is a solved problem. The bigger issue is that the high-angels / high-demons know tricks to punish immortals. Imprisonment of souls is a nifty trick.
First Strike +1/+1 and Indestructible.

User avatar
Xanthir
My HERO!!!
Posts: 5228
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex
Contact:

Re: Coding a Data-Driven Roguelike (Again)

Postby Xanthir » Wed Mar 01, 2017 10:19 pm UTC

Yeah, the D&D 3e death mechanic is *really* narrow; in original 3e, you were unconscious and bleeding out from -1 to -10, and dead after that.

5e's mechanic is a lot better imo - once you drop to zero, you're unconscious and bleeding out (if the enemy was trying to deal lethal damage). You then *stay* at exactly zero until you either revive or die; normally this is done via the death save mechanic (flip a coin each turn; three total successes means you're stable but unconscious, three total failures means you're dead), but healing can also pull you back up. (There's also a very small edge-case in that if an attack would drop you to (-½ * your max health), you die immediately, but if that doesn't trigger, your HP immediately resets to zero again.)

In practice, this gives you a few turns of drama trying to revive your friend before they fail 3 death saves, which can be a lot of fun.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))

User avatar
Quizatzhaderac
Posts: 1510
Joined: Sun Oct 19, 2008 5:28 pm UTC
Location: Space Florida

Re: Coding a Data-Driven Roguelike (Again)

Postby Quizatzhaderac » Wed Mar 01, 2017 11:09 pm UTC

KnightExemplar wrote:Consider that Gandalf is no more powerful than a ~level 7 Wizard
Gandalf's class isn't the same as a D&D wizard's, so that's a specious comparison. His role in the plot is much more "Guy who knows stuff" than "guy who throws fireballs". Conversely one could say that Beowulf is L20 because he solos an adult dragon, even though he's, like, a level 2 fighter in terms of his special abilities and items. It's a matter of the setting how different kinds of power relate to each other and what's even possible as all.

If a setting wants death to be a transient thing that's fine as long as it is consistent and it either 1) resigns itself to undercutting dramatic potential 2) is willing to devote major narrative focus to the majorly altered state of the human condition.
The Great Hippo wrote:So you end up with a crypt with a bunch of leaderless undead, maybe a few adventurer corpses, and a troupe of kobolds far below being ruled by a demon -- as one example.
So how is that different that having a bag of different monster sets for the dungeon and pulling out those same results? I'm not arguing that it's not a worthy goal, but just that you need to consider the fine points of what you're trying to accomplish.

Procedural generation has less possibilities then completely random generation, but it allows for more consistency which allows for more meaningful content. Generating across time means you can create a consistent history; what do you want to do with that history?

I would also suggest as a first (And more achievable) goal generating by architecture.Like saying: okay, given that the first section is A, it makes sense for the next section to be B, C, or D, but not E, F, or G. This let's you control your thematic shits, but also gives you control over the dungeon's pacing.
The thing about recursion problems is that they tend to contain other recursion problems.

User avatar
Yakk
Poster with most posts but no title.
Posts: 11053
Joined: Sat Jan 27, 2007 7:27 pm UTC
Location: E pur si muove

Re: Coding a Data-Driven Roguelike (Again)

Postby Yakk » Mon Jun 12, 2017 8:16 pm UTC

I think switching to success/fail only is the wrong way to go. Accumulation leads to flattening of "instant lose/win".

* Make your random engine generate values.
* A "Check" transformation simply maps a value to 0/1.

So a Check against DC 20 is just a *roll* with a -20 penalty, fed into "Check" that turns it into true/false.

Damage now falls out.

Having a "success track" can be used to flatten random chance into something more predictible.

---

Modifiers themselves need attributes. "Double your proficiency bonus" is not the same as "add your proficiency bonus again". Instead, it is "roll.prof_bonus.modifiers += {"increase_prof_bonus", times(2)}". Then some rule needed to determine how they stack.

Somethign else that halves your prof bonus might "roll.prof_bonus.modifiers += {"decrease_prof_bonus", times(1/2)}".

3e and 5e and 4e D&D all have different ways these things stack. For each name, you find the one "most extreme" and only use it, for example.

Meta-modifiers make things more complex; a feat that says "when you double your prof bonus, add another 3" gets crazy. Find out if you ever need this.
One of the painful things about our time is that those who feel certainty are stupid, and those with any imagination and understanding are filled with doubt and indecision - BR

Last edited by JHVH on Fri Oct 23, 4004 BCE 6:17 pm, edited 6 times in total.


Return to “Coding”

Who is online

Users browsing this forum: No registered users and 9 guests