301 Days (redux)

Another metric leap year of gamedev experiments and such

Nov 6, 2016 - OldDays seitan-spin

Day 78a - Make Your Own Problems

Digital potions are the best. Digital potions are the best.

In which we start making our own potions.

Test Every Day

Now that we’ve done permanent stat effect, time to move onto the temporary effects. Only problem is, only one of them actually exists in the item catalog:

SELECT [catalogID]
      ,[itemID]
      ,[notes]
  FROM [production].[dbo].[CatalogItem]
  WHERE notes LIKE '%temporary%' AND baseType = 'Bottle'
catalogID   itemID      notes
----------- ----------- ---------------------------------------------------------------------------
94          20070       Temporary Strength Potion *ALL*
108         20210       Temporary Strength Potion *Oak*

(2 row(s) affected)

It’s time to start generating new items, rather than relying on what was in the production db. Add a generic potion “blank” to the minimal db:

EntireDB-minimal.sql GitLab
748
749
750
751
/*** Begin test items at CatalogItem 500, itemID 50000 */
/* Generic potion */
INSERT [dbo].[CatalogItem] ([catalogID], [notes], [combatAdds], [itemID], [itemType], [baseType], [name], [visualKey], [unidentifiedName], [identifiedName], [shortDesc], [longDesc], [wearLocation], [weight], [coinValue], [size], [effectType], [effectAmount], [effectDuration], [special], [minDamage], [maxDamage], [skillType], [vRandLow], [vRandHigh], [key], [recall], [alignment], [spell], [spellPower], [charges], [attackType], [blueglow], [flammable], [fragile], [lightning], [returning], [silver], [attuneType], [figExp], [armorClass], [armorType], [bookType], [maxPages], [pages], [drinkDesc], [fluidDesc])
   VALUES (500, N'Generic Potion', 0, 50000, N'Potable', N'Bottle', N'bottle', N'unknown', N'Bottle', N'Bottle', N'a bottle', N'a generic bottle', N'None', 0.2, 20, N'Sack_Only', N'', N'0', N'1', N'', 1, 4, N'None', 0, 0, N'', 0, N'None', -1, -1, -1, N'', 0, 0, 1, 0, 0, 0, N'None', 0, 0, 0, N'None', 0, N'', N'You drank the potion!', N'a generic liquid.')

and some code to make a new item from a blank and a hash of changes:

features/lib/db/item.rb GitLab
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def create_item_from_blank(lookup_hash, create_hash)
  client = connect_to_db(@server_database)
  query = db_field_from_hash('CatalogItem', lookup_hash, '*')
  debug_msg "Query is #{query}"
  result = client.execute(query)
  row = result.each(:first => true, :symbolize_keys => true).first
  debug_msg row.inspect
  new_row = row.merge(create_hash)
  debug_msg new_row
  new_row.delete(:catalogID)
  new_row[:itemID] = find_next_item_id()
  query = db_insert_from_hash('CatalogItem', new_row)
  debug_msg query
  result = client.execute(query)
  affected_rows = result.do
  fail "Unable to insert item!" if 1 != affected_rows
  new_row[:itemID]
end

so we can create a step to make new potions:

features/steps/character_steps.rb GitLab
44
45
46
47
48
49
Given(/^I create (a|an) "([^"]*)" potion of amount "([^"]*)" and duration "([^"]*)"$/) do |aan, effect, amount, duration|
  @item_id = create_item_from_blank(
    { itemType: 'Potable', baseType: 'Bottle', effectType: '' },
    { effectType: EFFECT_TYPE_ENUM.index(effect.downcase.to_sym).to_s,
      effectAmount: amount, effectDuration: duration})
end

and use it to churn through the temporary stat potions:

features/player_effects.feature GitLab
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
## Temporary_Strength, Temporary_Dexterity, Temporary_Intelligence, Temporary_Wisdom,
##   Temporary_Constitution, Temporary_Charisma
# Note: We're also verifying that we get temporary boosts even if we're at the max for the Land.
Scenario Outline: Temporary stat potion increases stat temporarily
    Given I use the "minimal" database as-is
    And I add player and character "TestTS01"
    And I set the character's "<stat>" stat to "<init>"
    And I create an "<effect>" potion of amount "<amount>" and duration "2"
    And I put the item in the character's left hand
    And I start the server and play
    And I have a "<stat>" stat of "<init>"
    When I open and drink "bottle"
    And I have a temporary "<stat>" stat of "<amount>"
    And I rest
    And I have a temporary "<stat>" stat of "0"
Examples:
| stat         | effect                 | init | amount |
| strength     | Temporary_Strength     | 10   | 1      |
| strength     | Temporary_Strength     | 18   | 2      |
| dexterity    | Temporary_Dexterity    | 10   | 3      |
| dexterity    | Temporary_Dexterity    | 18   | 2      |
| intelligence | Temporary_Intelligence | 10   | 1      |
| intelligence | Temporary_Intelligence | 18   | 2      |
| constitution | Temporary_Constitution | 10   | 3      |
| constitution | Temporary_Constitution | 18   | 2      |
| charisma     | Temporary_Charisma     | 10   | 1      |
| charisma     | Temporary_Charisma     | 18   | 2      |
| wisdom       | Temporary_Wisdom       | 10   | 3      |
| wisdom       | Temporary_Wisdom       | 18   | 2      |

The stumbling block here was that these temporary stat increases aren’t reflected in the show stats output, but instead require a show tempstats to be seen.

So now that works, and I went back to retool the permanent stat effects the same way.

Tackle a TODO every other day

This looks like a good one:

features/config_file.feature GitLab
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# TODO: Make more reliable, keep player from missing repeatedly.
Scenario: DisplayCombatDamage True, I can enable and see combat damage
  Given I use the "minimal" database
  And I set "DisplayCombatDamage" in the config file to "True"
  And I add player and character "TestCombat01"
  And I add an immediate NPC spawn of a wolf on the spawn point
  And the server is started
  When I log on as "TestCombat01"
  And I enter the chat
  And I issue the display combat damage command
  And I enter the game from chat
  And I wait for "wolf" to appear
  And I attack the "wolf"
  Then I saw numerical combat damage

Unreliable tests are worse than useless. Time is wasted chasing bugs that don’t exist, confidence in the tests is undermined by “sometimes it just fails”, and eventually problems slip through that should have been caught. Let’s nip this one in the bud, as it were.

Two issues with this test: if the player misses, they don’t do any damage, and can’t see whether it was reported numerically or not. And there’s a (slim) chance that the wolves will kill the player before the attack. Luckily there’s one solution to both: we give the player such a high weapon skill that both hitting and blocking are guaranteed.

features/config_file.feature GitLab
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
Scenario: DisplayCombatDamage True, I can enable and see combat damage
  Given I use the "minimal" database as-is
  And I set "DisplayCombatDamage" in the config file to "True"
  And I add player and character "TestCombat01"
  And I set the character's "unarmed" skill to "10000000"
  And I add an immediate NPC spawn of a wolf on the spawn point
  And the server is started
  When I log on as "TestCombat01"
  And I enter the chat
  And I issue the display combat damage command
  And I enter the game from chat
  And I wait for "wolf" to appear
  And I attack the "wolf"
  Then I saw numerical combat damage
  And I remove the spawn

(A note about line 264: With random-order testing, and with many tests using the database “as-is”, leftover spawns were causing problems. There’s nothing worse than a character being mauled to death by wolves when you’re trying to test potion effects. So I added code to remove a spawn zone from the database.)

features/lib/db/player.rb GitLab
228
229
230
231
232
233
234
235
236
237
def set_character_skill(player_id, skill, desired_value)
  client = connect_to_db(@server_database)
  query = "UPDATE [#{@server_database}].[dbo].[PlayerSkills] SET \
    #{skill} = #{desired_value} \
    WHERE playerID = '#{player_id}'"
  debug_msg query
  result = client.execute(query)
  affected_rows = result.do
  fail "Unable to update player skill!" if 1 != affected_rows
end

Surprisingly, this database code was failing because I had not been adding a player skill entry when adding a player (and the game server hadn’t complained about that). So I also added that logic to player creation.

With an unarmed skill of ten million, the player has no problem surviving and doing some serious damage to the wolves:

Swing hits with heavy damage! (24)
The wolf is blocked by your hand.
The wolf misses you.
The wolf is blocked by your hand.
 ->

Problem Effects

Getting a complete passing run of these tests should not be difficult, but I kept running into random failures around the new PlayerEffect tests. A little investigation showed that effects were staying in the database, so when the same test was run again, results were not as expected. A quick hook to clear things out should help:

features/lib/hooks.rb GitLab
37
38
39
After('@clear_player_effects') do
  clear_player_effects()
end
features/lib/db/player_effects.rb GitLab
 7
 8
 9
10
11
12
13
14
15
def clear_player_effects()
  client = connect_to_db(@server_database)
  query = "UPDATE [#{@server_database}].[dbo].[PlayerEffects] SET \
    EffectID = 0, Amount = 0, Duration = 0"
  debug_msg query
  result = client.execute(query)
  affected_rows = result.do
  debug_msg "#{affected_rows} player effects cleared"
end

and then…

103 scenarios (103 passed)
969 steps (969 passed)
34m42.281s

And now we have confidence to add even more tests. Tomorrow.


Useful Stuff


More to come

Day 78a code - tests