301 Days

A year of gamedev experiments.

Day 66 - Ancestral Blog

| Comments

In which we start converting to plain(ish) Jekyll in earnest, and do the other things too.

Test Every Day

Setting aside the more involved “Combat damage in-game” task for the moment, what’s the next thing to test? Ah, UnderworldEnabled. If it was in the original game, it was definitely added after my time. Let’s see what the config option does.

DragonsSpine/GameSystems/Rules/Rules.cslink
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
public static void EnterUnderworld(Character chr)
{
    if (System.Configuration.ConfigurationManager.AppSettings["UnderworldEnabled"].ToLower() == "false")
    {
        chr.WriteToDisplay("This feature is currently disabled.");
        return;
    }

  try
  {
        if (!chr.IsDead) { chr.SendShout("a thunderclap!"); }
      chr.WriteToDisplay("The world dissolves around you.");

and there are a few ways to get to this code:

  1. Speaking the Ancestor Start Chant (“ashak ashtug nushi ilani”) in the ancestor start cell
  2. Speaking the Underworld Portal Chant (“urruku ya zi xul”) in the underworld portal cell
  3. Your corpse is burned while you’re still online and with positive karma
  4. Your corpse is burned while you’re still online and of Evil alignment, and fail a constitution saving throw
  5. You are older than “ancient” and lose a die roll (roughly 1/20 chance per round)
  6. Rest when dead with positive karma, in a map with no karma res point, if not a thief
  7. Rest when dead but not lawful, in a map with no karma res point, if not a thief

Ancestor Start

Given that characters in IoK did actually age and die, “ancestoring” was introduced as a way to pass down some of a player’s accomplishments to a new character. Like so many other things, it had to be done at a particular site with a particular set of magic words.

So how do we mark a map cell as an Ancestor Start cell?

DragonsSpine/World/Map.cslink
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
    else if (cell.CellGraphic[0].ToString() == "a")
    {
        switch (cell.CellGraphic)
        {
            case "a0":
                cell.IsAncestorStart = true;
                break;
            case "a1":
                cell.IsAncestorFinish = true;
                break;
        }
        cell.CellGraphic = ". ";
    }

So it’s baked into the map file. Easy enough to update the Test01 map to include it. We’ll throw in an Ancestor Stop and Underworld Portal cell while we’re at it:

1
2
3
4
5
6
7
8
9
10
11
<z>0</z> <x>40</x> <y>25</y> <f>light</f> <n>Test01 Surface</n> outdoor=true
WWWWWWWWWWWWWWWWWWWWWWWWWW
WW. . . . . . . . . . . WW
WW. . . . . . . . . a0. WW
WW. . . . . . . . . . . WW
WW. . . . . . . . . a1. WW
WW. . . . . . . . . . . WW
WW. . . . . . . . . gg. WW
WW. . . . . . . . . . . WW
WW. . . . . . . . . . . WW
WWWWWWWWWWWWWWWWWWWWWWWWWW

Ok, let’s try these two easy tests:

features/config_file.featurelink
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
Scenario: UnderworldEnabled False, the Ancestor Start Chant does not take me there
  Given I use the "minimal" database
 And I set "UnderworldEnabled" in the config file to "False"
 And I add player and character "TestAncestor01"
 And I move the player to the Ancestor Start cell
 And the server is started
 When I log on as "TestAncestor01"
 And I enter the game
 And I speak "ashak ashtug nushi ilani"
 Then I saw a message "This feature is currently disabled."

Scenario: UnderworldEnabled True, the Ancestor Start Chant takes me there
  Given I use the "minimal" database
 And I set "UnderworldEnabled" in the config file to "True"
 And I add player and character "TestAncestor01"
 And I move the player to the Ancestor Start cell
 And the server is started
 When I log on as "TestAncestor01"
 And I enter the game
 And I speak "ashak ashtug nushi ilani"
 Then I saw a message "The world dissolves around you."

Building on what we already have, there’s almost zero significant code to be written for these. Even moving the player (in the database, while they’re not logged in) is simple:

features/lib/db/player.rblink
82
83
84
85
86
87
88
89
90
91
92
def move_player_to(player_id, x, y, z)
  client = connect_to_db(@server_database)
  # [xCord], [yCord], [zCord]
  query = "UPDATE [#{@server_database}].[dbo].[Player] SET \ 
    xCord = #{x}, yCord = #{y}, zCord = #{z} \
    WHERE playerID = #{player_id}"
  puts query
  result = client.execute(query)
  affected_rows = result.do
  fail "Unable to update player!" if 1 != affected_rows
end

TODO/Refactor Every Other Day

The big TODO in the test code that leaps out at me today is the verbosity of the output. I’ve been remiss, leaving in raw debug output when the code is done.

The output from running the current 34 scenarios with debug off is 1480 lines. (With debug on, it’s 3880.) Let’s abstract out a debug_msg method and search for all of the putss. We’ll retool the telnet output as well while we’re at it.

features/support/env.rblink
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def debug_msg(message)
  puts "#{message} # #{caller.first.partition("seitan-spin/").last}" if ENV['TEST_DEBUG'].downcase == 'true'
end

def telnet_out(string)
  unless string.nil? || ENV['TELNET_TRACE'].nil? || ENV['TELNET_TRACE'].downcase == 'false'
    puts(ENV['TELNET_TRACE'].downcase == 'html' ? ansi_html(string) : ansi_strip(string))
  end
end

def ansi_strip(string)
  @terminal ||= AnsiSys::Terminal.new
  @terminal.echo(string)
  @terminal.render(:text)
end

def ansi_html(string)
  @terminal ||= AnsiSys::Terminal.new
  @terminal.echo(string)
  "<tt>" + @terminal.render(:html, 80, nil, AnsiSys::Screen.default_css_colors(false, true)) + "</tt>"
end

(Note: I also changed DEBUG to TEST_DEBUG because it was putting the server in debug mode, which was not the intention.)

So let’s see how chatty we are now:

  • TEST_DEBUG false, TELNET_TRACE false: 357 lines.
  • TEST_DEBUG false, TELNET_TRACE text: 2930 lines.
  • TEST_DEBUG true, TELNET_TRACE html: 3112 lines.
  • TEST_DEBUG true, TELNET_TRACE html: 4038 lines.

That seems a bit better, and paging through the non-debug output shows me only what Cucumber has to say.

Improve The Blog Itself Every Third Day

I finally found a Jekyll theme I can make a good start with, the Jekyll Dark Clean Theme. I tweaked it a bit and converted the first few days. More work is needed, but I think I’ll be able to switch to it soon.

It was surprisingly difficult to find a theme that fulfilled my requirements: Codeblocks that work well (with line numbers), multiple tags on a post, easily inserted images (that render correctly), and title and/or side bars that were not intrusive.


Useful Stuff


Day 66 code - tests

Comments