Jump to content
  • Advertisement
  • entries
    451
  • comments
    1239
  • views
    779315

About this blog

Adventures of Goblinson Crusoe

Entries in this blog

 

DungeonBot3000: Inventory and Items Display

The challenge is over, but that doesn't mean I'm done working on this thing. Diablo-clones are a core part of my fundamental nature as a human being, so of course I was going to get addicted to working on this project. Main things I've been working on have been tweaking the on-screen display of loot item tags to eliminate overlaps: and I have started work on building the inventory and equipment management systems and UI: UI is a fiddly, annoying beast to work on, and these kinds of inventory control systems with their UI are some of the worst. Lots of little edge cases, a whole lot of widget wrangling, some pretty complicated interactions etc... Luckily, the world has advanced since the days of Diablo 1/2, so I have plenty of existing models for how it should function properly. This system is far away from the levels of polish that will be necessary, but it's getting there. For the UI tags, I tested several different methods, and finally settled on the simple scheme of collecting all items within a radius of the player, sorting them based on their world coordinates so that they are in back-to-front order as seen from the player's view, then inserting their labels one by one into the label container for display. If a label to be inserted overlaps any existing labels in UI space, move the label downward until it no longer collides. While there is occasional flickery weirdness this way as the player moves, it seems to be at least on a par with Diablo 2, and doesn't suffer from some of the weird inaccuracies that Path of Exile suffers from. As far as the inventory system, I am at the point where picking up items places them in your bag, after first attempting to place them in an open equipment slot appropriate for their type. Can't drop anything yet, and can't yet move from bag to equipment and back, but that'll be on the menu for tonight/tomorrow.

JTippetts

JTippetts

 

DungeonBot3000: Postmortem

I've gotten done about all I'll be able to on this project before the deadline. It was pretty fun to do, even if I didn't end up with as much time for it as I would have liked. Still, I did manage to implement all requirements, so I have that going for me which is nice. The zip file is live now on the project page:   You can watch some current gameplay (with lots of me dying) here: Be warned: lots of deaths. I'm kinda terrible at my own game. So, postmortem. I've been tinkering with isometric dungeon crawlers and ARPGs off and on since I played Diablo 1 back in the late 90s, so I've gotten a bit of practice at some things. Still, this effort managed to teach me quite a bit. I didn't reuse much of that old code; almost everything this time was written from scratch for this project. The game is written using the Urho3D engine, using the OpenGL backend. Usually I write mostly in Lua, but this time it's straight C++. The data files come as a mix of XML and JSON documents. The premise of the game is that you play an experimental battle robot from centuries ago, tasked with eliminating the evil Cult of Gamed'ev. This cheesy lore skin, of course, satisfies the "minimum one gamedev.net reference" requirement. The game is an ARPG. You have access to 2 skills (I had hoped for more, but ran out of time): a spinning blades attack (akin to the Whirlwind skill of Diablo 2 barbarians, or Cyclone from Path of Exile) and a channeled laser beam attack. These skills are boosted by equipping a Blade (for the spin attack) and a Laser Beam(for the laser, obviously). Items are randomly generated, can have multiple mods in classic ARPG style, and are gained by killing mobs. Currently, there are 3 types of items: Blades, Lasers and Shells, and they can draw from a fairly broad pool of random modifiers including Increased Life, Life Leech, Damage Reduction, etc... Items come in Normal(white), Magic(blue), and Rare(yellow) varieties. There are also 4 base types for each, with each base type being more powerful than the previous ones, and hence only dropping on lower levels of the dungeon. I wasn't able to implement an inventory system in time, so to equip items you simply click on them in the world and the item you click on will replace the one you are currently using in that slot. Hovering over an item on the ground will show info boxes for both the item you are looking at and the one you are already using in order to compare. You progress through a maze that consists of 10 labyrinthine levels. Stairs up/down are used to transition between levels. Each level is populated by a random assortment of Users (regular gamedev forum members, non-named), Moderators (larger, redder, and having a small number of random mods; named based on the current gamedev.net moderator list), Emeritus (even larger, purple, and with more mods; named from the GDNet Emeritus member list) and Staff (green, very large, and named, only 2 variants: Khawk and jbadams). Khawk and jbadams reside on the very bottom, Level 10, of the dungeon. As you progress through the floors, mob density grows larger. As well, the mobs scale based on the dungeon level, in both health and damage dealing, so that the very last level is quite dangerous indeed. What went right: Lots of things really came together on this project: I drew inspiration for a stats container system from my Goblinson Crusoe project, implementing a system whereby I can have stats drawn from various sources, that all work together to provide complex systems from simple parts. The combat system is definitely something I'd like to flesh out more fully in the future. What went wrong: @Rutin kindly found a crash bug on level transition late last night, almost at zero hour. I was able to fix it, but due to the time constraints there are a lot of other sloppy things in this project that need to be refactored. I did a last-minute revamp of the game state handling system to fix this bug, but there are certainly other bugs lying in wait.  Also, balance is sort of ROFL. I have managed to get to L10 using Blades, with lots of death and respawning (luckily, if you respawn you keep your equipment and restart on a fresh instance of your current level). I haven't made it that far with lasers yet, and I haven't managed to kill Khawk or jbadams at the current numerical values yet. They roll with 5 random mods, so they almost always end up with both Quick and Berserk, which when coupled with Life Leech and Life Regen, make them basically unkillable at current power levels. Someone out there might have better luck/skill/determination than me, though, so if you manage to kill them let me know. The UI is rudimentary-to-nonexistent. I hacked the bare minimum to get by, but it's not very user friendly. Luckily, it's an ARPG so all you really gotta remember is: Right Mouse Button to kill, Left Mouse Button to loot or RUN AWAY! You can use 'q' and 'w' to switch between Spin Attack and Laser Beam. Given more time, I wanted to implement at least an inventory screen to manage equipment, but alas... Mobs are not varied in their attacks. Users, Moderators and Emeritus all do just a single Kick attack. I If I get a chance today before the deadline, I might try to add a little bit more variety to their attacks. Khawk and jbadams have custom attacks, at least. Other details: For the Credits, I implemented an Energy system. You use Energy when you attack, slowly, but you can also spend it using a button strategically placed in the upper righto corner, to gamble on an item. This gamble rolls an item from the full pool of item bases, and rolls it at Level 10 so you can potentially get any mod. I've never gotten anything that useful from it, though. I had intended to make gambling reachable by using a kiosk of some sort in-game, but ran out of time.

JTippetts

JTippetts

 

DungeonBot3000: Boss Fight Scripting

I've been working on scripting/AI for the final boss fight, wherein you fight Khawk and jbadams.   The fight arena isn't set up yet; in this video, I just spawn a bunch of Khawks and jbadamses, just to see how they play out. Khawk carries a shoulder-mounted fire grenade launcher, while jbadams carries a big-ass ban hammer. He can leap, plus he does a telegraphed slam attack for beefy damage. I'll probably give Khawk another ability or two, maybe a heal of some sort plus possibly a Summon Moderator skill or something. Assuming I have time, that is, because it's running out. Next up, I'm going to go back to items and make them droppable and equipable. For the challenge, I'll probably have to skip making any kind of inventory UI. I'll probably just make it so that you click an item in the world to equip it automatically, and drop your currently equipped item in that slot. Once I do items, I'll need to do a quick pass over scene management for more control over spawning, build the boss arena, and build some sort of level progression. 

JTippetts

JTippetts

 

DungeonBot3000: Friggin Laser Beams

Implemented the second player skill: Laser Beam. It's a channeled skill that targets the cursor, with a build-up that increases damage dealt the longer you channel. Stop-casting and casting again resets the build-up, and the beam has a tracking speed that can be increased with mods that increase attack speed, so it doesn't exactly follow the mouse cursor's speed. This video also showcases Moderators and Staff (scaled-up versions of the User, with red and green shirts), as well as the newly-implemented Reflect Damage monster mod. (Green-shirted staff in this video rolled the reflect mod at 100%, which is why my life drops when I laser them.) Working on more mods and triggers. I'll probably do 1 more player skill, then I want to implement a few more enemy skills and the boss skills for KHawk and jbadams in the final boss fight. After that, I'll throw some sound effects on the attack triggers and some music in the background, plus work some more on items and make items actually drop as loot. Feeling the pinch of time, though, since I will be working 12-hour shifts for 8 of the remaining 17 days. The final product is certainly going to be an unbalanced mess, since right now I'm focused on mechanics and not numerical balance.

JTippetts

JTippetts

 

DungeonBot3000: Video

Some video to show the progress made so far:   Pardon the crap quality. I still haven't learned how to youtube properly. And one of those foreseen things is happening today, specifically having to go back to work for a run of night shifts. So progress is going to slow down drastically now. Hopefully I'll be able to tick off a few more of the requirements before the deadline.

JTippetts

JTippetts

DungeonBot3000: Stats, Combat, and Items

I have to admit to an ulterior motive or two when it comes to this Challenge entry. My other project, Goblinson Crusoe, will certainly benefit from some of the iteration on stats, combat and item design that I am doing. In fact, it has already benefited. For one thing, I've polished the way I handle combat stats. In a previous entry I talked about a system I had built to implement combat stats in GC. While the system had some good ideas, the implementation was clunky as hell, and the data file format was painfully verbose and awkward. To sum up, I built a system of stat containers, or sets, that held stats and managed the various linkages between them. Dependencies could be specified in JSON files, using specialized description formats. Stats could depend upon other stats in various ways; for example, the MaximumLife stat could depend upon Level (the character's level), IncreasedMaximumLife boosts, and so forth. The engineering challenge was to come up with a way to manage having stats and stat modifiers coming from various sources: equipment, base character stats, buffs/debuffs, and so forth. If you read that previous entry, you can see that the first iteration of the system was ugly and the specifiers for stat dependencies were convoluted and extremely limited. The code backing that shit up was even worse, I assure you. In revisiting the idea for this challenge entry, though, I decided to clean up and simplify the way I build the stat sets. The first example from that previous entry now would look like this: { "MajorStatPerLevel: [["Flat", "5"]], "MinorStatPerLevel": [["Flat", "2"]], "Bravado": [["Flat", "5"], ["Flat", "Level*MajorStatPerLevel"]], "Arrogance": [["Flat", "3"], ["Flat", "Level*MinorStatPerLevel"]], "Cunning": [["Flat", "3"], ["Flat", "Level*MinorStatPerLevel"]] } Gone are the various "StatFlat", "CalcLinear", "Scale", etc... specifiers. In their place is a simple expression parsing system. A stat modifier is specified as a Type (can be one of Flat, Multiplier, Scale, Min or Max), and an expression string that is parsed to provide the dependency linkages. Stat values are calculated by summing the Flat contributions, multiplying them by the sum of the the Multiplier contributions + 1.0, multiplying that result by the product of the Scale contributions, and capping them by the values of the Min and Max contributions, if specified. StatValue = SumOfFlat * (1.0 + SumOfMultiplier) * (Scale*Scale*Scale...) Additionally, I have expanded the system to implement things like global or local mods, and mods from various sources. An action, be it an attack or other combat-related activity, requests from the combatant the relevant set of stat sets as a StatSetCollection. If it wants to use the local mods for the weapon equipped in the left hand, it requests those. If it wants to use the stats for a certain skill, it requests those. This collection is used to evaluate the final values for the stats required for an action. A simple example. In the game currently, DB3000 can perform a spin attack. The spin attack uses the damage values specified by the equipped blades (the item system is still in development, so those are hard-coded for the moment), as well as the character's base stats and a set of damage values specified by the spin attack spell itself, in order to calculate the final damage values for the attack. Say that DB3000 has a Steel Blade equipped: "Steel Blade": { "MinLevel": 8, "Fixed": ["SteelBladeImplicit"], "Random": ["PhysicalDamageLocalTiers", "LifeRegenTiers", "EnergyGenTiers"]} }, This item structure specifies the fixed implicit (local) mods for a Steel Blade, as well as the set of mod tiers that can be randomly rolled on a Steel Blade. The SteelBladeImplicit mod StatSet looks like: "SteelBladeImplicit":["Implicit", "Damage: 20 to 50", { "PhysicalLow": [["Flat", "20"]], "PhysicalHigh": [["Flat", "50"]] }], So a basic Steel Blade deals physical damage in the range of 20 to 50. The Spin Attack skill itself also provides a StatSet: { "SpinAttack": { "PhysicalLow": [["Flat", "5+SpinAttackLevel*5 + PhysicalDamageToSpinAttackLow"]], "PhysicalHigh": [["Flat", "10+SpinAttackLevel*8 + PhysicalDamageToSpinAttackHigh"]], }, } This means that using Spin Attack will add physical damage to the physical damage the blades provide, based on the skill level of the Spin Attack skill. It will also add damage based on any PhysicalDamageToSpinAttack bonuses provided by equipment or buffs or other sources, if any. When the skill is used, all of these various StatSets are concatenated into a single set (conceptually, at least) then the final value for PhysicalLow and PhyiscalHigh are determined, and a damage roll is made. A Level 1 Spin Attack would do 30 to 68 damage with a Steel Blade with no random mods. Items can also roll a list of randomized mods. One such mod that a Steel Blade can roll comes from the PhysicalDamageLocalTiers set, which provide local (meaning, they apply only to skills using the equipped blade) physical damage modifiers: "PhysicalDamageLocalTiers": { "Weighting": 2, "Tables": [ {"Level": 1, "Mods": ["Cruel"]}, {"Level": 5, "Mods": ["Barbarous", "Cruel"]}, {"Level": 10, "Mods": ["Brutal", "Barbarous", "Cruel"]}, ] }, From this tier list you can see that the physical damage mods come in 3 tiers. A Steel Blade can start to drop on Dungeon Level 8 and up, so if one drops on L8 it can only roll from the set including  "Cruel", or from the second set including "Barbarous" and "Cruel", which are specified as: "Cruel":["Local", "Increase physical damage by 10 to 15.", { "PhysicalLow": [["Flat", "10"]], "PhysicalHigh": [["Flat", "15"]] }], "Barbarous":["Local", "Increase physical damage by 20 to 35.", { "PhysicalLow": [["Flat", "20"]], "PhysicalHigh": [["Flat", "35"]] }], If the blade drops with the Barbarous mod, then that will provide an additional flat 20 to 35 damage to the Spin Attack. These stats exist as StatSets local to the item, so they are collected only if the item is equipped and, since it is a local mod, only if the skill is using the blade, which Spin Attack does. So, once the various StatSets (from blade and from the Spin Attack skill) are collected, you end up with: "PhysicalLow": [["Flat", "20"], ["Flat", "20"], ["Flat", "5+SpinAttackLevel*5 + PhysicalDamageToSpinAttackLow"]], "PhysicalHigh": [["Flat", "35"], ["Flat", "50"], ["Flat", "10+SpinAttackLevel*8 + PhysicalDamageToSpinAttackHigh"]], for the final physical damage calculation of the Spin Attack. At Level 1, that means it does 50 to 103 physical damage per hit. I know this has been a bit long-winded. A lot of this is second- or even third-generation stuff derived from what Goblinson Crusoe already has, with many much-needed fixes, so when I'm done working on this challenge I'll be able to fold a lot of this back into GC. It should be fun.

JTippetts

JTippetts

 

DungeonBot3000

I wanted to take part in the latest Challenge, Dungeon Crawl. Unfortunately, due to things both foreseen and unforeseen, I haven't had a whole lot of time to work on it. Nevertheless, I will carry on even if I'm pretty sure I won't be able to finish all of the requirements. You are DungeonBot3000, a prototype combat droid built during the ill-fated Uprising. After 650 years, the nefarious Cult of Gamed'ev has once again arisen in the depths, led by the reanimated avatar of KHawk, freshly-emerged from cryo-sleep. After so many centuries, your systems have degraded and your power generation capabilities are severely handicapped; nevertheless, the dictates of your directive are clear: eliminate the Cult. Arming yourself with castoff bits of tech you find along the way, you must enter the depths of Gamed Dungeon, the system of caves and tunnels from within which the evil Cult waged their Uprising so long ago, and within which the new Cult has emerged. Fight your way through Anonymous Posters, Users, Crossbones and powerful Moderators as you quest into the depths to confront your ultimate targets: KHawk and his Staff. Along the way, find bits of technology to help you boost your power generation and upgrade your abilities, enhancing your speed and combat effectiveness. But beware: as the Cult brings their defenses online, the areas you must clear will become more and more deadly. So far, I've got a basic (and sufficient for the challenge) maze gen system in place, and have begun work on the controllers and combat. Enemies spawn in groups and are driven by the Detour Crowd functionality of Urho3D, to pursue relentlessly. The combat system is ARPG, and I've currently implemented a spin attack. More to come, though probably not before the deadline.

JTippetts

JTippetts

 

Diablo:Immortal

I have a hard time getting worked up about the Diablo:Immortal announcement, because I never really got over the bitter disappointment that was Diablo 3. Of course they're turning the Diablo franchise into reskinned generic mobile pay to win trash. It was obvious what path they were on with it after Diablo 3. I'm just a little surprised at how quickly they got there; I figured they would have squeezed out and pinched off at least one more awful installment before deciding to throw up the middle fingers to their legacy fans like that. Kudos to them for at least having the balls to announce it in possibly the worst venue they could have: in front of hundreds of fans devoted enough to shell out for a Blizzcon pass. Watching that guy's face fall off as the boos started stirred within me feelings that can only be described by unpronouncable German words.

JTippetts

JTippetts

Combat Stats

I am currently working on combat stats. I've worked on them before, but never really fleshed out a system I've been satisfied with. For the most part, I've gotten by with half-assed, or at best three-fourths-assed systems that always kinda change as my requirements change, but with no clear structure or rules. All of that half-assed code and system is gone now. In its place is basically a spreadsheet. In fact, it would make sense to define the data files underpinning it all as an actual spreadsheet. There are problems with that, though; namely that I'm terrible at spreadsheets, since I've never really used them. Sure, I have LibreOffice Calc on my laptop, and sure I've even fired it up once or twice. But I find the spreadsheet format to be clunky, and I really don't want to write a bunch of code to pull fields and expressions out of an XML document. So, I'm writing my data files by hand. It's actually not bad so far, though I can see the need to write a tool for it in the future. So, stats.  As a jumping off point, I want to implement a system similar to Path of Exile. My goal is to support player-side stats with a similar level of depth/and complexity, and to also support monster-side stats with the ability to scale enemies based on player level. In order to support rapid iteration and testing of concepts, the stats need to be mostly defined in data. Some underlying concepts (damage types, damage scaling, etc...) are hard-coded, but the stat relations are defined in JSON files that are specified as attributes of the CombatStats component that all combat-enabled units possess. If you are not familiar with Path of Exile, their system is a 3-stat based (Strength, Intellect, Dexterity) system, with 4 types of damage mitigation: armor (to mitigate physical), evasion (to dodge stuff), energy shield (to absorb damage) and resistances (for elemental damage types and chaos damage). Dexterity contributes to Evasion, Intellect contributes to Energy Shield, and Strength contributes to Life (which is stupid, imo; it should contribute to Armor instead, for the sake of balance). Body Armor, Boots and Gloves all have score requirements for Str, Dex and Int; there are high-strength armors that provide high Armor rating, high-int armors for Energy shield, etc... So in order to equip certain things, you need sufficient quantities of the relevant stat(s). Classes are also aligned with the three base stats, with one character class per stat, and one character class for each hybrid (ie, Templar is a Strength/Int hybrid class). Resistances stand outside of the 3-stat system, with each resistance being increased by mods on gear or from the passive talent tree. They are a percentage-based mitigation, with each having a maximum cap (starts at 75%) that can be raised through talents or equipment. Aside from the 3 stats, there are other ways to build a character. Simple passives such as +%increased Spell Damage, or +%increased Melee damage, and other simple number increases are available. More importantly, there are Keystone talents available on the talent tree, and special mods available on certain unique items, that provide more complicated stat alterations than simple number increases/decreases. For example, the Avatar of Fire keystone dictates that 50% of all physical, lightning or damage the player deals is converted to Fire damage, and the character deals nothing but Fire damage. This has a huge effect on how the rest of the character can build. In conjunction with an item that provides 50% of physical damage is converted to Fire, it means you can build a straight melee brawler who can deal massive physical damage, but have all of that damage convert to Fire with a possibility for Fire damage to ignite targets, causing a DoT Burn effect. So, in light of the fact that I would like to build something similar, I've come up with a fairly simple system. I have implemented a StatSet structure, which owns an unordered_map of stats. The stats are keyed by a string hash, so that they can be requested either by name or by the hash of the name. Requesting by name is useful for debugging/testing, and requesting by the hash is marginally more performant. A Stat is implemented simply as a linked list of mods. In earlier versions, I implemented Stat as a value plus a list of mods, where each mod was either a flat addition to the stat, or a multiplier. The final value of the stat, then, was calculated as (Base + (SumOfFlatMods)) * (1.0 + SumOfMultMods) In this setup, you can have mods that add a flat amount (say, +10) to a given stat, or you can have mods that increase a given stat by some percentage. Specifying a Mult mod as 0.5, for example, is tantamount to increasing the stat value by 50%. In the current iteration, I have done away with the Base value, since it's basically just a Flat mod in disguise. Now, a Stat is simply a list of mods that either apply a flat additive value or a multiplier. These mods can either be simple numeric values, or they can be derived from other stats. I have also added a mod type of Scale, which can be set to scale the final result of a mod by a value. This Scale value is normally 1, so that it has no effect, but if a mod of type Scale with a value of 0 is applied, then it can set the stat to 0. This facilitates stats such as the previously mentioned Avatar of Fire, by applying a mod of 50% to the ConvertPhysicalToFire (and other conversion stats), and by supplying a mod of 0 to the Scale field of the other damage types, as an example. Thus, a Stat's value is finally calculated as ((SumOfFlatMods) * (1.0+SumOfMultMods))*ScaleMod. (Scale mods are not calculated as sums, but rather as a direct replacement. You can specify multiple Scale mods, but only the last one encountered will apply.) Also allowed are mods that can fix the value of a stat to some specified maximum or minimum. By this means, I can cap a stat (for example, a resistance capped at 75%) and use another stat to specify the cap value (resistance max). I can also cap the bottom end of a stat, for example by clamping a resistance to a -1.0 value, meaning that you can't go any lower, and it's not possible to do more than double damage against a target due to negative resistance. This basement cap can, of course, be modified, opening up the potential for builds that are very powerful against negative-resistance enemies. These various relations are specifiable in a JSON file. In fact, I can load multiple JSON files into a StatSet. This allows me to break off common functionality into one file, and load class-specific files per-unit. The syntax of my data files is still a little bit clunky, but here is an example: { "MajorStatPerLevel": [{"Type": "Flat", "Value":5}], "MinorStatPerLevel": [{"Type": "Flat", "Value":2}], "Bravado": [{"Type": "Flat", "Value": 5}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MajorStatPerLevel"}], "Arrogance": [{"Type": "Flat", "Value": 3}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MinorStatPerLevel"}], "Cunning": [{"Type": "Flat", "Value": 3}, {"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "Level", "Scale": "MinorStatPerLevel"}], } In this example, I define two 'utility' stats, MajorStatPerLevel and MinorStatPerLevel, as flat values (5 and 2). Then I define 3 core stats (Bravado, Arrogance and Cunning, which are goblin-ish reskins of the more familiar Strength, Int and Dex). These core stats are defined using 2 mods apiece: a flat base mod, and a mod that applies a flat amount calculated as Level multiplied by a scaling factor. (The Level of a combatant is just another stat; in this way, by changing the Level stat, I 'automatically' get level-based scaling.) This snippet would scale Bravado higher than Arrogance and Cunning, and so would be appropriate for a Bravado-centric character class. A different file could be loaded for Arrogance or Cunning-based classes. (I could, of course, provide stats for hybrids, but I think 3 classes is quite enough for a solo developer to try to tackle.) This setup means that with each 1 increase in experience Level, the unit gains 5 Bravado and 2 of Arrogance and Cunning, on top of a base of 5, 3 and 3. To get the current value of Bravado, I can simply call StatSet::GetStat(name)::Get(), and the scaling occurs depending on the value of Level. To implement something along the lines of the Avatar of Fire keystone, I might do something like this: { "AvatarOfFire": [{"Type": "Flat", "Value": 0}, {"Type": "Max", "Value": 1.0}], "AvatarOfFireConversionScale": [{"Type": "Flat", "Value": 0.5}], "ConvertCrushToBurn": [{"Type": "StatFlat", "ModType": "CalcLinear", "Stat": "AvatarOfFire", "Scale": "AvatarOfFireConversionScale"}], "CrushDamageFinal": [{"Type": "StatScale", "ModType": "CalcOneMinusStat", "Stat": "AvatarOfFire"}], } In this case, Avatar of Fire acts as a simple flag. If 0, no real effect is applied. The CrushToBurn conversion is ignored in damage calculations since it's equal to 0. A damage value of type Crush will be processed, amplified using the CrushDamage stat (not shown here, but all damage types have one which is used to boost damage based on things such as +%increased Crush damage), and then scaled by the value of CrushDamageFinal, which is calculated as (1-AvatarOfFire)). Since AoF is 0, this amounts to a simple multiply by 1 operation. However, simply by adding a Flat mod valued at 1 to the AvatorOfFire stat, the behavior of the other stats changes. Now, the ConvertCrushToBurn stat is non-zero, with a value of 50%, so in the damage calculation after CrushDamage is applied, then 50% of the intermediate value is converted to a damage record of type Burn. Any remaining damage of type Crush, then, is scaled by CrushDamageFinal which is now equal to 0 (ie, 1-AoF), meaning that any Crush damage remaining becomes effectively 0. By supplying a similar set of mods for the other damage types, I have effectively modeled the behavior of PoE's Avatar of Fire passive talent. StatSets can be merged. For example, I can have the base StatSet for a character, then each piece of equipment can have its own mini StatSet that contains mods for just the stats it's interested in modifying. For example, a sword could have a mod for the JabDamage stat that increases Jab damage by 15%. When an attack is made using that sword, the combat system would copy the character's base stat set, then merge the swords stat set into it to get the final working set. This allows me to have equipment that can modify any of the stats, including flag-type stats such as Avatar of Fire. Thus, I could do things like "Equipping this armor grants Avatar of Fire". Removing the armor removes that particular set of stats from the merge. The system still has some jankiness. It's pretty clunky writing JSON files (my poor " key), plus the syntax of the various mods leaves quite a bit to be desired. Also, for the moment, the ability to merge StatSets applies only to simple numeric mods (can't use equipment to apply mods that rely on other stats, for example.) I don't see this being a problem at the moment, but it could be in the future. Still, for the relative simplicity of the underlying structure, it's given me quite a lot of flexibility in structuring the stat systems. I've got level-scaling of monsters, monsters that scale differently based on their JSON spec files, monsters with randomized mods, etc... It's pretty cool how it's all coming together. (Forgive the potato-quality of the stat sheet in the accompanying screen shot. This crap is way too much in flux right now for me to waste time building a custom UI widget yet.)

JTippetts

JTippetts

 

Drunks Standing Around Punching Each Other

A lot of years ago, I witnessed a fascinating exchange between three drunk college students of my acquaintance. The setting was a bonfire kegger in the hills, the setup was a masculine test of bravado and toughness, and the payload was funny as hell. These three fellows (who were all so drunk, they could barely stand up) thought it would be funny to see who was the toughest by punching each other in the face. No, it wasn't a fight. Blows weren't exchanged, fast and furious and drunkenly inaccurate. Instead, each would take a turn standing there motionless while one of the other two were allowed one shot. The receiver could spend a few minutes fortifying himself as he thought necessary (many beers were consumed), then he had to stand motionless while the other two guys did their level best to knock him down. In the end, it was pretty funny to watch. The entire exchange took probably an hour, and I think that if the participants had been less drunk, some real damage might have been inflicted. As it was, it was a comedy of errors: wild swings, missed punches, a couple cuts and a black eye. It was stupid, the kind of stupidity you might expect from such a setting, but what was remarkable to me is that each of the three respected the rules. When it was your turn to get hit, you took whatever was dealt out. This absurd scene came back to me (no, I was not a participant, though I was invited to partake) yesterday as I was working on Auras in Goblinson Crusoe. Auras are character-centered area effects that can apply any of a number of effects to other combatants within the radius: degens/damage, stat buffs or debuffs, slows or speeds, etc... They suffer from much of the same disjointed weirdness as I had to deal with when implementing damage-over-time and heal-over-time effects, all stemming from the abstraction of thinking of a turn in a turn-based game as having some sort of temporal coherency. You see, since the beginning of this project I have thought of a turn in terms of everything ostensibly happening at once, even if the actual actions are split out into seperate sections per combatant. Enemy A goes, then GC goes, then Enemy B, and so forth until the end of the turn and it all starts again, but it all happens 'at the same time'. However, introducing Auras is yet one more thing to come around and illuminate the holes in such an abstraction. Consider the case where Enemy A is wearing an Aura of Rotting Degeneration, which applies a damage-over-time payload of Rot damage (I have revised my damage types; more on that later) to all in the radius. During his turn, Enemy A runs at Goblinson Crusoe, takes a wild swing with a rusty sword then, laughing, runs away to try to put some space in between. Then, during Goblinson Crusoe's turn, GC runs after the laughing enemy to deal his own drubbing. There are a few ways I can deal with the Aura in this case: 1) Apply the aura's effect only whenever the enemy bearing the aura acts. In this case, the aura will apply Rot damage to GC every step, starting from when the enemy draws near enough to place GC within the radius. As soon as the enemy retreats far enough away, stop applying the aura. In this situation, when GC takes his turn and runs after the enemy, the effects of the Aura will not be applied because it is not the enemy's turn to act. 2) Apply the aura's effect only whenever the target/victim acts. In this case, when the enemy runs of to GC, smacks him, and runs away, the aura will not apply an effect. However, when GC chases after the enemy, as soon as he reaches aura range the aura will start hitting him with Rot damage. Of course, both of these cases seem absurd on the face. The aura should be hitting GC whenever he is within range to be hit, so whether the enemy is approaching or evading, or whether GC is pursuing, he should be getting hit with the degen. Which leads to the third case. 3) Apply the aura's effects both when the enemy is acting AND when GC is acting. So while the enemy approaches, smacks with a sword, and runs away, GC takes Rot damage at each step. Then, when GC pursues, once he comes back within range, he starts taking Rot hits again. Of course, the drawback to this third scenario is this: if all of this is ostensibly taking place "at the same time", then it is likely that GC will take more Rot hits during the space of the turn than he realistically should. For example, say a turn is 10 steps. Say that for 8 of those steps, GC is within range of the aura while the enemy is attacking. Then, say that for 5 of them he is within range of the aura while he is pursuing. That comes out to 13 hits of Rot during a 10 step turn, or 3 more than he should possibly be able to take. Chasing a solution to this led me down the usual rabbit-hole of kludges, attempting to total up times during the space of the turn when GC should take the hit, yada yada yada. Resolving these temporal idiosyncracies is a recipe for madness. And so, following the example of those drunken college boys, I threw the temporal abstraction right out the window. I no longer think of fights in a GC combat as happening as a melee, with actions being performed at the same time during a round. There just seems no way for me to happily resolve the contradictions. Instead, now I think of a GC combat as a whole bunch of drunk college students standing around, taking turns throwing punches at each other. When it's your turn to stand, you stand and take what you get coming to you, and if it knocks you out then that's the game. And when its your turn to act, you chug a beer, ball up your fist, and do your level best to knock somebody else right the #*@! out. However, if I switch abstractions like this, then I need to revise how I do DoTs. You see, if the combatants really are just waiting around and taking turns, then if GC has a DoT in place, he should be taking that DoT at every step of action for all the other combatants, until the DoT runs out. This is a change that I am definitely going to need to playtest soon; sadly, my usual playtesters aren't really available anymore and that means I might need to finally, after all these years, step outside the circle of my immediate friends and family for some feedback.

JTippetts

JTippetts

Lighting Settings, Doodads and Water

Been working some more on the editor. I've implemented the ability to tweak the lighting parameters of the level, including the Front (main) light color/brightness, the Back light color and brightness, as well as ambient and fog colors. A couple sliders allow me to control the fog distance and attenuation factors. I have also implemented tools to edit water. I can edit water directly, using a brush like the other editing tools, or I can control it via a filter to fill to a given level. I can also output to the water layer from a function node graph, and the water tools respect mask settings so that gives further ability to control water placement. I have also started work on object spawning. The way it works at the moment is that on application load I scan the Doodads folder. Inside this folder are a number of sub-folders, one for each doodad. A doodad has a specific setup in that each folder must contain a thumbnail image and an XML editor object definition that defines the structure of the object that will be imported into the editor. Another XML file defines the object as it is to be imported into the engine. The editor object is typically just a graphical placeholder, absent all the various game-play related componentry of the official object. After the Doodads directory is scanned, a master list of doodads is displayed on the left when the Doodads tool is selected, showing a scrolling list of all available doodads. On the right is a group window from which you can create and delete groups of doodads. When a group is created, a window appears above showing the currently selected group of doodads. Select a doodad from the master list and click Add to add it to the current set. In the doodad set, each doodad is associated with a Weight that determines the probability of selecting that doodad when applying the doodad brush. By adjusting the weights, I can make some doodads in the set spawn more commonly than others. After a doodad group is assembled, I can use a standard brush to 'paint' doodads onto the terrain. Applying a brush iterates the radius of the brush, and generates a random number per cell, then compares the roll against a probability factor adjusted by the brush hardness and power settings, to determine whether to spawn a doodad at the given location. If a doodad is to be spawned, one is randomly selected from the set based on the probability weights then instanced and added to the map. In addition to brush-based doodad painting, I can also run a filter to fill the entire map from a set. Both brush and filter respect mask settings, allowing me to use masks to constrain doodad placement to specific areas. Currently, only the Doodads folder is scanned. These are non-blocking, non-interactive scene decoration elements only. For game-play-enabled objects, I will likely use a similar system but with finer controls for single-object placement. Don't really want to paint Boss enemy spawns with a large-radius brush, I reckon. Or... maybe I do.... After I implement the object spawning system, I plan to clean up and semi-finalize the savefile format, then I need to make the necessary modifications to the game itself to load these levels. That shouldn't be difficult, since most of the pieces are already there.

JTippetts

JTippetts

 

Goblinson Crusoe Level Editor

Been awhile since I updated, but I haven't been idle. Mostly, anyway. The usual hindrances to my development time apply, of course, but I have made some progress. My big project lately has been to repurpose my terrain editor to build a level editor for Goblinson Crusoe. You can see some pictures of my initial efforts here: So far, I have implemented all of the functionality of the unfinished terrain editor (height editing, noise node graphs, etc...) as well as making a first version of spline groups to allow having multiple spline curves for use in road and river building filters. I iimplemented a color chooser for choosing the colors for main light (sun), back light, and fog/ambient colors. (Some of these changes, which are relevant to the original terrain editor, I have propagated backwards to the original project; others, though, are specific to the GC editor.) I have also implemented a slight change to the quad-planar shader the ground textures use, so that I can edit top textures separately from side textures. And of course, as with all large GC-related projects, it has initiated a round of major refactoring in the way that I load and construct levels. Next on the list is editable water, followed by spawn groups to allow spawning level objects, and saving those spawners. The way I'm thinking about spawn groups is to populate a spawns folder with a whole set of proxy objects. These proxy objects will include the graphical model for a level object, along with a Spawner component that provides an indirection link to the actual game object to spawn. This will let me do things like replace the spawned object without having to edit every instance of that object in the level, create spawn objects that randomly select from a set of objects at level load, and so forth. That way, I can randomize the contents of a level when it is loaded, rather than having to randomize at level construction and provide multiple 'copies' of a level for randomized variants. I'm still thinking about the exact particulars of how I want to set this up. I'm also working on a format and spec for randomly generated levels. Some set-piece levels will be static (or, at least, randomized only on certain contents) while others will be wholly randomly generated as they currently are. I hope to tweak the editor to facilitate construction of the various functions that will be used to shape and populate these random zones. That should keep me busy for another several months/years, given the sparsity of the actual time I get to spend on this. (If my wife would let me quit my job and start living off savings and retirement, it'd be a whole lot faster. For a little while, at least, until those bank balances run low.  )

JTippetts

JTippetts

 

Resistances and Defenses

I've just come off a several-months-long jag of playing Path of Exile. PoE has influenced the (sporadic) development I've done through that time on Goblinson Crusoe by a great deal. While GC is turn-based, it shares a lot of the same DNA as PoE, specifically in the influence of Diablo and Diablo 2, so a lot of ideas and mechanics from those games have been seeded throughout GC. Like Diablo 2, Path of Exile implements a system of resistances for elemental damage (fire, ice, lightning). Resistance stat values are obtained primarily through gear, and are obtained as percentage values that accumulate up to a maximum value. For example, you could get a belt with +35% lightning resistance. Resistance amounts from gear and other sources are accumulated, then capped to a maximum value (75% by default), with the option of obtaining small increases to this maximum value via other sources. Resistance either reduces or increases (it is possible to have negative resistances that actually boost the damage the player takes) the incoming damage by the given percentage.  As one progresses through the game, at certain checkpoints the player's resistance value has imposed upon it a penalty. In the beginning, this penalty was imposed in smaller stages as one progressed through the difficulty levels. (Difficulty levels simply repeat the story of the game, with higher-level monsters as well as the resistance penalty coming into play.) In current PoE, the penalties are imposed at two checkpoints within the story: the first after completing Act 5 (character level approximately L45) and the second after completing Act 10 (around 67 to 70 character level). The first checkpoint applies a -30% reduction to resistances, and the second checkpoint another -30%, for a total of -60%. I understand the thinking behind this design. The game is balanced around having maximum resistances. That is, any given encounter will be damage-tuned with the assumption that the player is at resistance cap, and thus not having the resistance value at cap can bring extra punishment and pain. This provides a constant pressure for equipment improvement as one progresses to end-game; gear that was fine before the checkpoint now is suddenly deficient, pressuring the player to seek upgrades. At a certain point, though, the player can obtain enough +res% to overcome the penalties and still raise their resistance to 75%, meaning they are effectively "done" with upgrading their resistances. (Further equipment upgrades for other stats must be chose to maintain these resistance caps, but that is usually not too difficult.) Typical players are encouraged to obtain these caps as soon as possible to ease the leveling and progression process. While I understand the design, I've always been bothered by the implementation. Having gear that was "fine" at one point suddenly become "totally deficient" in one instant after beating a single specific boss feels too abrupt. Also, I kinda don't like that at a certain point the pressure to maintain resistances eases up. So in GC,  I am exploring ideas for putting this resistance penalty system on a smooth curve, rather than having the abrupt steps. The current iteration of this system uses a logistic function, which is a type of sigmoid function. Instead of collecting gear that provides +X% resistance to a given damage type, you collect gear that gives +Y resistance rating. This resistance rating is plugged into a logistic function to obtain the actual amount of resistance % to apply against incoming damage. The logistic function is structured like this: function res(rating, levelbase, levelslope) return (1.0 / (1.0 + math.pow(e, -levelslope * (rating - levelbase)))) *2.0 -1.0 end The function operates using the rating (granted by equipment and other buffs) as well as a base rating for a given level, levelbase. At level M, if the player's rating is equal to level base, then the granted resistance value will be 0%. Rating less than levelbase results in a negative resistance, while greater than levelbase grants a positive resistance. The factor levelslope is used to affect the spread of the function at a given level; ie, how quickly the resistance approaches 1.0 or -1.0. For example, if you use a levelslope of 1, that means that the resistance value will be very close to -1.0 at a rating that is 6 points below base, and will be very close to 1.0 at a rating that is 6 points above base. This slope value determines the slope of the curve where it passes through the origin of the graph. By making the slope shallower as the character level increases, that spread can be made wider, granting a larger window within which the rating will grant a resistance value somewhere between -1 and 1. This way, as resistance ratings grow larger, the absolute difference between the rating and the levelbase has a more gradual effect on the value. These graphs show how this works: At a levelslope of 1, you can see here that around 6 points below the levelbase, the curve approaches -1, and at around 6 points above, it approaches 1. So if the base resistance rating for that level were, say, 10 then if you had a rating of 4, you would have a resistance value of -100% (or close to), meaning you would effectively take double damage, whereas if you had a rating of 16, you would have a resistance of 100%, meaning you would take no damage. Now, at a higher level, you might have a levelslope of, say, 1/3: Here you can see that the spread is now approximately -16 to +16 from level base. If the levelbase rating for that level were, say, 100 then if you had 84 rating or below you would take double damage, whereas if you had 116 or higher you would take no damage. Of course, the base and slope ratings would be a candidate for a great deal of tuning in the final game. The constant increase of levelbase applies constant pressure for the player to upgrade resistance, not simply at 1 or 2 main checkpoints, but all throughout the game, with that pressure growing larger the longer one plays and levels up without changing equipment. And this also doesn't account for having a resistance cap. In PoE, the default cap is 75%, which can be raised only through rare and special means, which is a sensible design decision in my opinion. A simple solution for this would be to multiply the output of the resistance function by the value of the cap if the output is positive (leaving the negative resistance value uncapped). Doing it this way, the positive side of the curve approaches the resistance cap, rather than 1.0, while the negative side is untouched. I could even implement a negative resistance cap, if so desired, to allow the player to build a stat to reduce the amount of damage taken from having a negative resistance. In my preliminary tests (which include no playtesting so far by anyone but myself) it seems to work fairly well, but this is one of those kinds of systems that I will need to tinker with and explore more fully in the final testing phases. Just because it works well now, doesn't mean it won't be massively exploitable in the future.   I have also been tweaking and experimenting with damage types. At the moment, I have a system of damage types somewhat similar to PoE, though with quite a few differences. In my system, any given attack or spell can deal a certain combination of damage types selected from the set {Crush, Slash, Burn, Poison, Bleed, Void, Shock}. These damage types can additionally be tagged with modifiers drawn from the set {Projectile, Melee, Attack, Spell, Area, DoT, Siege} which can be used to apply damage increases or reductions. So as an example, a basic fireball of some sort might deal 2 damages. The first would be tagged {Crush | Area | Spell} and the second would be tagged {Burn | DoT | Area | Spell}. Player stats exist that can amplify or reduce damage dealt by any of these various tags. So, for example, it might be possible to have a stat that increases Spell damage by 13%, meaning both damage rolls from this fireball spell will be boosted by 13%. Primary damage types all come with a secondary debuff effect. Crush causes a stun/slow effect, slowing the target by some amount for some period of time. Slash damage causes a Bleed debuff that causes damage over time (this damage bearing the {Bleed | DoT} tags). Poison damage also applies a stacking debuff to poison and burn damage resistance rating, meaning that poison and burn damages become more potent the more poison stacks there are. Void causes an increased chance to take a critical strike, and Shock increases all damage taken by a certain %. These effects are all subject to change as the game develops further. The idea, though, is that each damage type should be differentiated by a mechanic, and not just by a type. I've played games where there was no mechanical difference between, ie, Fire and Lightning, merely cosmetic differences and the necessity of maintaining resistances against two types instead of just one. If a damage type doesn't lend itself to some mechanical difference from the others, then it will be altered or removed.   At the moment, all damage types are mitigated in a similar manner, using the damage resistance calculations describe earlier. That is, 'physical' types such as Crush and Slash are not mitigated using any kind of armor rating, but instead are mitigated by Crush or Slash resistance rating granted by certain equipment. Homogenizing the various damage mitigation strategies in this manner vastly simplifies the design of the character combat back-end and balancing, though again it is subject to change in the future.

JTippetts

JTippetts

 

U3DTerrainEditor Project Page

I've created a project page for the U3DTerrainEditor I have been working on. Project now includes a downloadable Windows binary so you can try it out. Warning: rough code ahead. It's still a pretty unpolished project, but you can do some cool stuff with it regardless. Your first terrain: 1) Download the archive, unpack, and execute either run_opengl.bat or run_d3d11.bat. Alternatively, you can execute TEdit.exe or TEdit_D3D11.exe directly, passing arguments on the commandline; ie,  TEdit.exe LuaScripts/terraineditor.lua -w to run windowed instead of borderless. The batch scripts execute in borderless windowed mode. 2) Click on the NodeGraph button in the toolbar, represented by 3 boxes linked by lines. A window will popup in the lower right. Click new to create a new Node Group, then click Edit to edit the new node group. An overlay window will popup with a + button to create nodes, and an output widget. 3) Click on +, navigate to library, then click on ridged. A node for a Ridged fractal should appear. Change the value of the Frequency input to 3. Click and drag on the output link of the node, and drag the link to the input of the output widget. When the link is established, click the Preview button. The preview pane should fill with a preview image of the node function. 4) At the bottom of the output widget, ensure that Terrain is selected as the Output Target, then click Execute. After a brief processing pause, you should see the terrain change in the background. Press 'm' to close the node window. 5) Click on the Filters button. (It looks like a magic wand). From the list of filters, select Erosion. In the Power box, enter an erosion power (or just stay with 0.3, which is a decent choice). Power values can range from 0 to 0.5. Click Execute. After a brief prcoessing delay, the terrain should change again showing the results of the erosion applied. 6) From filters, select the Cliffify filter. In the dropdown box for Layer, select Layer 7. Press execute. After processing, the terrain should change so that steep areas show a cliff-like texture. 7) Select the Cavity Map to Layer filter. In the Layer dropdown box, select Layer 2, then press execute. The terrain should change again, showing sandy texture in the bottom of hollows and depressions. 8) Press the Terrain Options button (represented as a gear wheel). Select the Save button next to the Terrain: label, enter a filename, and hit Save to save the terrain heightmap to a PNG file. Heightmap will be saved as a standard RGB PNG, with height stored in the Red and Green channels. Red is the coarse height, and Green is finer detail data, allowing the heightmap to represent more than just 256 levels of elevation and making a smoother heightmap. Height can be extracted as Red + Green/255.0f. Of course, there is a lot more you can do with it. There are brushes for manually editing terrain, texture layers and masks. You can use masks to protect areas from being edited or modified. You can create a spline (use 'q' and 'e' to remove and create knots) then use the Road filter or River filter to create areas of road and river. You can export the texture blend layers as well as a normal map. Some stuff still might not be hooked up right, and as always this is a work in progress. If you have any feedback or suggestions, let me know.

JTippetts

JTippetts

 

ANL Editor, GC Editor, The Future

Following along from the previous post about the node graphs, I have lately pulled the node graph stuff out and have started work also on a standalone editor for noise functions. https://github.com/JTippetts/ANLEditor The editor uses the node graph functionality, along with an output node that provides various functions, to allow one to create graphs of noise that can be used to create textures. The output node allows you to map either a Grayscale or RGBA image output (the Volume button currently does nothing, for now). It can analyze a connected grayscale function to give you a histogram graph of how the function output is distributed, and calculates a set of scale/add factors that could be used to remap the output of the function to the 0,1 range. It allows you to specify seamless mapping settings, and to export images to file. It's all still fairly rudimentary and I still haven't settled on a final save/load format, but all that is in progress. I have also started creating an editor of sorts for Goblinson Crusoe, using some of this editor functionality. It's still in its infancy, but eventually it will allow me to create areas and area pieces for use in randomly generating maps.  Lately, I've been doing some thinking about what I want to do with Goblinson Crusoe. It has become clear to me that it will probably never be a commercial release. It will probably never be on Steam or anything like that. I just don't have the time. I wasted a LOT of my early years spinning my wheels and going nowhere, and now I have so many other things that have to come first (family, full-time job, home-ownership, etc...) that I just don't think I'd ever realistically finish this thing. If I could work on it full-time, perhaps, but mortgage, bills, and the necessity of providing insurance and safety nets for my wife and kids means that isn't feasible. However, I do enjoy working on it, and don't want to just abandon it. Nor do I want it to never see some kind of release. And I would like to see some kind of return on my huge time investment over the years. So I've been thinking of making GC a free and open-source project, linked with a Patreon and/or PayPal for goodwill donations. At least that way, if I die or life gets in the way of further development, at the very least it wouldn't disappear completely. Plus, I might get at least a few dollars from appreciative people along the way. What do you all think? Any advice on how to structure such a thing? Good idea/bad idea?

JTippetts

JTippetts

 

Node Graphs and the Terrain Editor

I've been working on the node graph editor for noise functions in the context of the Urho3D-based Terrain Editor I have been working on. It's a thing that I work on every so often, when I'm not working on Goblinson Crusoe or when I don't have a whole lot of other things going on. Lately, it's been mostly UI stuff plus the node graph stuff. The thing is getting pretty useful, although it is still FAR from polished, and a lot of stuff is still just broken. Today, I worked on code to allow me to build and maintain a node graph library. The editor has a tool, as mentioned in the previous entry, to allow me to use a visual node graph system to edit and construct chains/trees/graphs of noise functions. These functions can be pretty complex: I'm working on code to allow me to save these graphs as they are, and also to save them as Library Nodes. Saving a graph as a Library Node works slightly differently than just saving the node chain. Saving it as a Library Node allows you to import the entire thing as a single 'black box' node. In the above graph, I have a fairly complex setup with a cellular function distorted by a couple of billow fractals. In the upper left corner are some constant and seed nodes, explicitly declared. Each node has a number of inputs that can receive a connection. If there is no connection, when the graph is traversed to build the function, those inputs are 'hardwired' to the constant value they are set to. But if you wire up an explicit seed or constant node to an input, then when the graph is saved as a Library Node, those explicit constants/seeds will be converted to the input parameters for a custom node representing the function. For example, the custom node for the above graph looks like this: Any parameter to which a constant node was attached is now tweakable, while the rest of the graph node is an internal structure that the user can not edit. By linking the desired inputs with a constant or seed node, they become the customizable inputs of a new node type. (A note on the difference between Constant and Seed. They are basically the same thing: a number. Any input can receive either a constant or a seed or any chain of constants, seeds, and functions. However, there are special function types such as Seeder and Fractal which can iterate a function graph and modify the value of any seed functions. This is used, for example, to re-seed the various octaves of a fractal with different seeds to use different noise patterns. Seeder lets you re-use a node or node chain with different seeds for each use. Only nodes that are marked as Seed will be altered.) With the node graph library functionality, it will be possible to construct a node graph and save it for later, useful for certain commonly-used patterns that are time-consuming to set up, which pretty much describes any node graph using domain turbulence. With that node chain in hand, it is easy enough to output the function to the heightmap: Then you can quickly apply the erosion filter to it: Follow that up with a quick Cliffify filter to set cliffs: And finish it off with a cavity map filter to place sediment in the cavities: The editor now lets you zoom the camera all the way in with the scroll wheel, then when on the ground you can use WASD to rove around the map seeing what it looks like from the ground. Still lots to do on this, such as, you know, actually saving the node graph to file. but already it's pretty fun to play with.

JTippetts

JTippetts

 

Node Graph UI for Accidental Noise Library

So the last couple days I've been working on something that I've wanted to do for a long time now. I've been building it as part of a terrain editor I've been working on. It's still mostly uncomplete, but so far you can create nodes, drag links to link/unlink them, then output them to a greyscale image. In the works, I've got a few node types to implement, and a lot of glue code to implement saving/loading node graphs, hooking them up to generate terrain and terrain layer weights, etc... But it's coming along pretty nicely. In the process, I have made a few fixes and additions to the noise library, to help with matters. One of the main additions is the Fractal node. The library, of course, has had fractals since the beginning, but the way they were implemented made it tough to allow them in the node graph editor. So instead, I implemented a special function type that can take as input a chain of functions for the layer noise, as well as other functions to provide the fractal parameters. Internally, the function will iterate over the number of octaves, and calculate the noise value. At each octave, the layer function chain is re-seeded with a new seed. This travels the function graph, and sets a new seed for any values of Seed type in the chain. This small change has opened up some easier ways of making fractals. Additionally, I have added a Seeder module, which implements this internal re-seeding operation. I have implemented also a Randomize module. The randomize module takes a seed or seed chain, and uses it to randomize an output value from within a range. It's kinda weird to talk about, so instead I'll demonstrate using a real-world solution to a common problem. Here is a fractal image of ridged noise: This fractal is generated by layering successive layers, where the input is a Value noise basis passed through an Abs operation before being summed with previous layers. It creates the Ridged Multifractal variant, but you can see in that image that there are grid-based artifacts. These kinds of artifacts are common; each layer of noise is generated on a grid, and so the artifacts tend to sort of amplify as the fractal is built. You can mitigate it somewhat using different values for lacunarity (which is the value that scales the frequency for each successive layer) but that can only slightly reduce the visible appearance of artifacts, not eliminate them altogether. A long time ago, I advocated for applying a randomized axial rotation to each layer of noise, rotating the noise function around a specifiable axis in order to un-align the individual grid bases, and prevent the grid biases from amplifying one another. Previously, these rotations were built directly into the fractals, but that is no longer necessary. The new Randomizer and Fractal nodes now make this easy to incorporate in a more flexible way (or eliminate, if you really want artifacts): In this screenshot, you can see that I have set up a fractal node, and for the layer source I specify a gradient basis fed through an Abs function. That function in turn feeds a RotateDomain node, which feeds the layer input of the fractal. Attached to the angle input on the fractal is a Randomize node, that randomizes a value in the range 0 to 3. The result is this: You can see that the grid artifacts are gone. The fractal iterates the layers, and at each layer it re-seeds the Layer input chain. This means that any node marked as a seed is re-set to a new value each time. This means that the Gradient basis node (which has a seed) is re-seeded, and the Randomize node that specifies the rotation angle is re-seeded. This means that each noise layer generates a different pattern than the other layers, and is rotated by a different amount around the Z axis. This misaligns the grid biases, preventing them from amplifying each other, and gives a nice non-artifacty fractal pattern. I still have quite a bit to do in implementing the rest of the noise functions in ANL. But there you go, that's what I'm working on right now.

JTippetts

JTippetts

 

Diablo 3 Resource Bubbles

It's been awhile since I posted. I've been kinda reluctant to just post more project updates on GC, since that kind of thing gets a little tedious and pretentious sometimes. "Hey, guys, take five minutes out of your precious time to look at this screenshot of an incremental improvement to creature AI, that doesn't really show anything that I've actually been working on because that kind of thing doesn't show up well in static screenshots! But ain't it SHINY?!" GC is still in progress; at least, as much as it can be given all the other stuff going on. Kid starting kindergarten, camping for several days in Yellowstone, other kid starting preschool, work (as always), yardwork, working some more on the basement, etc, etc, etc... While thinking about some further UI upgrades, however, I was doing some reading through old links. I was thinking about healthbars, and re-visited a link from my bookmarks that dealt with the resource bubble meters in Diablo 3. Diablo 3 was an awful game, still one of my most hated, but damned if Blizz can't make things shiny and pretty. https://simonschreibt.de/gat/diablo-3-resource-bubbles/ speculates a little bit about how the resource bubbles are implemented graphics-wise, and I thought it might be interesting to take a stab at making one, even if ultimately it isn't useful for Goblinson Crusoe. The article above talks about diving through the D3 assets and finding a circular mesh with distorted UV coordinates that provide the basis for the spherical distortion of the moving texture map. However, I elected to go with the idea mentioned in https://simonschreibt.de/gat/007-legends-the-world/ of using a texture map that encodes the UV coordinates for the spherical distortion. To generate the map, I fired up Blender and created a sphere and a custom Cycles material for it. After some thrashing around, the node setup I ended up with was this: The material takes the normal of the sphere and splits it into X, Y and Z components. It multiplies X and Y by the cosine of Z, then scales and translates the results into the range 0,1 by multiplying by 0.5 and adding 0.5. Then it sets the red channel from the X result, the green channel from Y, and applies the resulting color as an emission material with a power of 1 (which outputs the color without any lighting or shading). The result looks like this: The way this texture is used is that the resource bubble is drawn as a rectangular plane, texture mapped across its face from (0,0) in the upper left to (1,1) in the lower right. This texture coordinate is used to sample the bubble UV texture, and the result is used to sample the diffuse color texture. This causes the diffuse color to be distorted as if it were being drawn across the surface of a sphere: For the diffuse color, I grabbed a random photo of stars, snipped a piece, and made it seamless. To achieve the complex-seeming swirling motion effect, the star map is duplicated 3 times, with each layer being translated by some vector multiplied by elapsed time, and the three layers multiplied together and scaled up. You can't see the motion in a still photo, of course, but in real-time it has 3 layers being moved in different directions at different speeds, and the effect is actually quite mesmerizing. To implement the resource bubble representing different levels, I added a uniform to the shader, Level, that can be specified as any value in the range 0,1. To set the specific level, you set the value of the uniform based on the ratio of the resource's current value to it's maximum value. ie, for mana, you would set the uniform to (CurrentMana / MaximumMana). This level is used with a smoothstep function to cut-off the diffuse texture at a given height. Of course, using the smoothstep as-is would create a straight cut-off, so to make it more interesting I provide a noise texture and use the noise texture to distort the cut-off function. This noise texture is animated, and the effect is to make the top of the resource fluid look 'frothy'. Also, a clip texture (in a real application, I would probably make this the alpha channel of the normal texture instead of a separate texture) is used to clip the outsides of the bubble to black. Now, I felt that the surface of the fluid, even with animated froth, looked a little plain. And if you look at the D3 resource bubble, there is a 'line' of glowing material on the surface of the fluid that provides emphasis to the level. So to implement that, I used another pair of smoothstep functions, based on the fluid level, to isolate a 'band' centered on the top of the fluid. This band is used to scale the brightness of the fluid, and is distorted by the same noise texture as the fluid surface. This gives the effect that light is shining on the surface of the liquid, making it stand out. Finally, I overlaid a texture that contains the reflections/streaks for the glass. To implement this texture, I used the sphere in Blender and applied a glossy shader with some lights. This one was done in haste, but it looks okay regardless. In a real application, I would spend a little more time on that one to make it look better. This glass texture is applied additively to the final fragment color. In motion, it looks pretty cool:   The final GLSL shader code for Urho3D looks like this: #include "Uniforms.glsl" #include "Samplers.glsl" #include "Transform.glsl" #include "ScreenPos.glsl" varying vec2 vTexCoord; varying vec4 vWorldPos; uniform sampler2D sGlass0; uniform sampler2D sClip1; uniform sampler2D sNoise2; uniform sampler2D sNormalTex3; uniform sampler2D sStars4; uniform float cLevel; void VS() { mat4 modelMatrix = iModelMatrix; vec3 worldPos = GetWorldPos(modelMatrix); gl_Position = GetClipPos(worldPos); vTexCoord = GetTexCoord(iTexCoord); vWorldPos = vec4(worldPos, GetDepth(gl_Position)); } void PS() { float level=1.0-cLevel; vec2 newuv=texture(sNormalTex3, vTexCoord).xy; float clip=texture(sClip1, vTexCoord).x; float maskval=vTexCoord.y+texture(sNoise2, vTexCoord+vec2(0.1,0.3)*cElapsedTimePS).x * 0.05; float mask=smoothstep(level-0.01, level+0.01, maskval); float glowline=min(smoothstep(level-0.05, level, maskval), smoothstep(level+0.05, level, maskval))*clip*5+1; gl_FragColor=clip * mask * texture(sStars4, newuv + vec2(0.1,0)*cElapsedTimePS) * texture(sStars4, newuv + vec2(0.01,0.03)*cElapsedTimePS) * texture(sStars4, newuv + vec2(-0.01,-0.02)*cElapsedTimePS) * 4.0 * glowline + texture(sGlass0, vTexCoord); } If you want to see it in action, here is a downloadable Urho3D sample (I can't promise it'll stay active for long; I tried to upload it as an attachment, but it kept failing mysteriously): https://drive.google.com/file/d/0B_HwlEqgWzFbR0R6UzJGUTJTSHM/view?usp=sharing Github repo: https://github.com/JTippetts/D3ResourceBubbles On Windows, extract the archive, navigate to the root, and execute the run.bat batch file. It should open up a 256x256 window with the animated resource bubble filling the frame.   This little project was a fun little diversion while waiting to take my daughter to preschool. I don't tinker with shaders very often anymore, so it was nice to do so. It shows how even cool effects like this can be relatively simple underneath.

JTippetts

JTippetts

 

Water

So, I ended up doing the "skirts" method I spoke of in the last post. And in conjunction with the default Urho3D water shader (with a few small tweaks, and more to come to eliminate artifacts on the corners of the water blocks) it actually looks pretty decent. The water animates a noise texture that ripples the ground beneath. It also uses a reflection texture (which I have set to black at the moment) if desired. I might tweak the water shader further, but for now I'm happy with it. I've also got all the small issues sorted out from the change to multi-level water. It wasn't a large change, but I was surprised at how many different parts of the code worked from the assumption that water was determined by elevation < 9. I thought I had contained it better than that, but I did get all the various spellcasting, pathfinding and object spawning oddities worked out.

JTippetts

JTippetts

 

Multi-level Water

So, I'm trying to figure out how to do water. Right now, I am doing water the "brain dead" way; any tile below a certain height is water, and water is created as a simple hexagonal plane with a partially transparent blue material applied. It works okay, but the ultimate end goal is to have water play a more involved role in the landscape. I'd like to have rivers, waterfalls, etc... and that means that I need to rethink how I do it. I'm trying to come up with ideas for the geometry of water. Here is a shot of how the current water system might look if I use it unmodified for multi-level water: Clearly, I need some sort of geometry to tie the pieces together. My first thought is to create a sort of "skirt" piece that is attached to the side of a water hex if that water hex has neighbors whose water height is lower than its own. Might end up looking something like this: The issue with that, of course, is that I have to oversize the skirts to avoid Z-fighting with the underlying ground hex, and that means that skirt pieces overlap with adjacent skirt pieces on different hexes and with the water hex of the lower water levels. In combination with the alpha-blending, this creates bands or regions of darker color where two pieces of water blend together. I could use waterfall particle systems to help obscure this overlap, I think. Alternatively, I could use a solid material instead of a partially transparent one: I dont like the look of it, though. Large areas of flat water look terrible. Granted, there will need to be improvements to the actual water material to make it look better regardless of how I handle the geometry, but for now I'm sorta stuck on how best to do this. I do have some ideas as to how I could perform the geometry stitching of the skirts to minimize overlap, but it'll take the creation of special pieces, and some special-case code. Not too difficult, I suppose, but still seems kinda messy.

JTippetts

JTippetts

 

New Gameplay Video

I'm on vacation in California, which means I kinda have some time to work on stuff, but it's split up by blocks of frantic activity. I'll tweak a few things, then head off to Knott's Berry Farm to burn in the sun while the kids ride on rides too small for me. Then I'll fiddle a few more things, then take the kids swimming. So while I'm getting some stuff done, it's all in a sort of disorganized tangle. I did decide to upload a new gameplay video. Once again, it's pretty poor quality. (Some day, I'll own a rig decent enough to make high-quality videos. But that day is not today.)   It's about 10 minutes of random gameplay. I was kinda distracted while playing by a 5 year old who wanted my help building electronic circuits with a snap kit toy he recently got, so there are some pauses. Also, there is still stuttering in some spots, which won't be cured until I fully convert everything from Lua to C++. (That's an ongoing project, but I am getting quite a bit of progress done while here in Cali.) The stuttering is from the Lua garbage collector, which has been an ongoing problem with the Lua bindings to Urho3D. Throughout development of this game it has been at time worse and at times better. A recent change (unsure which one) made it worse again, enough that I'm finally going to just convert everything except UI stuff to C++ components.  

JTippetts

JTippetts

 

Doors, Dungeons, MakeHuman, Bug Fixing

It's been a little bit of an art push lately. First of all, I started work on a dungeon tile set. Up there is my first stab at it. I created a couple different wall variations, a door and a hex-pattern tile ground texture (used in conjunction with existing sand and gravel textures). Don't have anything in the way of doodads or decorations yet. Doors are still kinda tricky. I had a conversation with riuthamus about it. The gist of doors in this game is that a door needs to work with any configuration of walls around it, so trying to do artwork for a traditional-looking door and choosing alternates to match up with the surrounding walls was getting to be too difficult. I had already implemented doors some time ago that utilize portcullis-like behavior: when you open the door, it slides into the ground. Closing it brings it back up again. The door in the above shot works the same. The issue lies in creating a graphic that looks door-like, even though it doesn't look like a traditional door. I'm not sure there's a perfect solution for it. But at least when you hover over a door, a popup appears with the label 'Door'. Hopefully that's enough of a clue for people to figure it out.   I've also started experimenting with MakeHuman. The ogre in this shot is a result of that experiment:   It was a quick effort. I just used some of the clothes provided with MakeHuman (hence the jeans and button-up shirt, articles of clothing that would be quite difficult to obtain in the Goblinson Crusoe universe) and ran some of the various sliders for the mesh deformation all the way to 11 to try to get an ogre-ish form. The experiment worked pretty well, I think, certainly well enough to warrant further experimentation. As a bonus, MH will export a skeleton rig to fit the mesh, though I still have to rig it with IK and animate. As it turns out, I'm still terrible at animating. Who knew?   I spent some more time doing miscellaneous cleanup. Fixed a bug that caused creatures to die multiple times if they died in a round with multiple dots on them. (They would die once for each dot because I wasn't checking for isdead in between dot applications.) Formalized the construction of summoning spells, so that a flashy spell effect is played when things are summoned. Added some flashy effects for things dying. Moved and rearranged some data tables again. You know, crazy shit like that.  

JTippetts

JTippetts

 

The Weirdness of Turn-Based Games

So, lately I've been working on the DoTs/HoTs mentioned in the previous entry, as well as the framework for ground effects: ignited ground, lava, etc... And in the process I have yet again stumbled upon exactly how weird a turn-based game really is; or, at least, one done in the manner in which I am making this one.
Here's the setup: Goblinson Crusoe is built on an Action Points-based turn system. A Turn consists of 10 Rounds. Each Turn, all entities that want to act are gathered into a list, then each are given an opportunity to act for 10 Rounds. Moving, casting spells, attacking, harvesting loot, etc... these all consume Action Points until all points are used up, or until the unit chooses to end its turn early, at which point the next unit in line is given the chance to act. When all units have acted, the next Turn is started. So, while the units perform their actions consecutively, the abstraction is that these actions are ostensibly happening "at the same time". That's the weirdness of a turn-based game. You take a turn, then I take a turn, but it's supposed to be like these turns happen all at the same time. The abstraction really breaks down upon analysis, and there's really no way to make it better outside of moving it to a real-time simulation.
One way that this flawed abstraction bit me in the ass is with these DoTs/HoTs and ground effects. You see, in this system, time only really passes for a unit if that unit actually acts during a Turn. For example, control passes to the player who moves 10 hexes. That's 10 rounds worth of time, and at each step an event is fired to indicate a round has passed. DoTs, HoTs and time-based ground effects wait for these events in order to know when to do their thing. After all, you can't have an over-time effect without the "over time" part.
The problem I ran into is that while mobs such as enemies, GC, GC's minions, etc... are all active, acting objects, some things that otherwise can take damage are not. Things such as walls and doors, which have no active combat capability. They block tiles until destroyed, that's it. And the weirdness that resulted is that these units were effectively immune to DoTs. No time passed for them, so no ticks of damage were ever applied.
That's not what I wanted. I mean, obviously, a burn effect should burn a wooden door down.
It took a little bit of figuring to come up with a workaround that didn't completely break the system. The workaround is that walls and doors and such ARE active objects, but they have a different aggro component and an ai controller that does only one thing: ends its turn. The normal aggro component collects damage events and tracks them according to various damage sources, and if any damage was taken or any proximity events occurred, it signals that the unit is ready and willing to act that Turn. The fortification aggro component, however, only tracks DoT/HoT and ground effect events. If any of those are applied to the unit, then the unit wants to act. If they all expire, then the unit no longer wants to act. In the case of fortifications, "acting" means to simply end it's turn without doing anything. End Turn will cancel out any remaining Action Points, add them up, and send off a Time Passed event for the amount remaining, meaning that an entire 10 Rounds worth of time will be sent for the unit at once. The result is that if any fortification is hit for periodic damage in a Turn, then the camera will pan briefly to that unit while the damage numbers tick off, then will move on.
It doesn't really feel optimal, but I guess it's about the best I can do. The turn-based system in GC is fairly zippy, and simulation speed can be increased if desired, but it's still not great that the player has to spend some few seconds of a combat Turn watching DoTs hit all of the walls engulfed by the last Inferno spell he cast. But still, it seems to work okay.
It's always nice when I get these relatively large, relatively unbroken blocks of time in which to get things done. And it's always sad when they come to an end. Night shifts at work start tonight, and I'm working some extra days in the next couple weeks, so this thing'll probably be put back on the back burner once again. :(

JTippetts

JTippetts

 

DoTs, HoTs, Aggro Bugs, Smarter Siege-breaking, Banners

Added a quick text banner on level load, to give the player a hint or reminder of the scenario type. While right now the scenario type is "random collection of enemies scattered across a randomized terrain", eventually I'll have different scenarios such as "boss fight", "base assault", "base defense", "dungeon crawl", etc... This, I hope, will help to provide variety in the kinds of gameplay a player can take on.
A couple days ago, I implemented a DoTs and HoTs system. Damage-over-time, heal-over-time. Spells and effects can apply dots or hots (either timed or permanent) which deal or heal a set amount of damage of a set of specified type(s). The way the system works is that whenever a unit is acting (moving, casting, attacking), the amount of "real" turn time (ie, actual action points used, not modified amounts used due to speed modifiers) is accumulated and used to apply damage or healing whenever time accumulates to one round or longer. So, as the unit is walking along, periodically there will be damage applied or healing done.
Now, the DoT/HoT system is working pretty well. However, in the process I uncovered another bug in the AI. When I first went to test the system with a test DoT, I noticed that certain units would stop acting correctly after a DoT would hit them. These units would work correctly before the DoT, but once the first damage was applied they would stop moving or acting in any way. Confused, I dug into the code. Turns out, in the Aggro component (which tracks damage sources and is used to figure out who the unit hates the most) I wasn't filtering incoming damage sources based on origin. It wasn't a big deal before, when all damage applied to a unit was external; however, the DoT damage is tagged as being applied by the unit itself. This resulted in the mobs damaging themselves, getting pissed at themselves, and trying to kill themselves using skills and attacks that can't be used on themselves. It was a simple fix, but demonstrative, I think, of the kind of weird shit you have to tackle when doing AI.
Tonight, I rewrote and cleaned up the heuristic function for enemy pathfinding as well. It runs faster, making pathfinding more snappy. I also started tweaking AIs to better handle incidental enemy units encountered while pathing, for example to bust through a barrier or wall owned by Crusoe. The additions and changes make them much more effective at busting through any fortifications GC puts up. I still need to do more work on this, though, as there are still a few oddities. Still, it is nice that GC can no longer sit untouchable by melee units behind a ring of walls.

JTippetts

JTippetts

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!