Sunday 30 August 2009

Zoom!

One of the last things left on the UI to do is to implement a real-time zoom:


Ideally, I'd like this to be controllable via the Scroll-Wheel of the mouse as well as with keyboard shortcuts.

Saturday 29 August 2009

Making the most of the screen

In the next release, you will be able to minimise the information window by pressing F1:


And restore the standard view by pressing F2:

This works even if the screen is resized. But what information should be displayed along the top of the screen in the full view?

Wednesday 26 August 2009

This was unexpected...

I didn't expect this.

Tuesday 25 August 2009

Refactoring Progress #3

With real life interfering quite a bit, things are going quite slowly on the refactoring front - It also doesn't help that I'm in the middle of doing some of the larger units (e.g. UnitFunctions, UnitEngine, UnitDisplay, UnitCreature etc).

Here is the current progress:


In other news, I'm having another wibble over licensing. This is interesting and seems to indicate that I can release Kharne as GPL (instead of MPL) (this and this may also be relevant). Does anyone have any experience of this with regards to Delphi and use of 3rd party custom components? I may end up just emailing the FSF to clarify.

Friday 21 August 2009

Kharne on Linux....

Running via WINE, courtesy of elig (from the comments below).

Thursday 20 August 2009

The Crawl Poll

There's a Crawl Poll out. Crawl is my favourite roguelike. Hence are my answers.

1. What is you age?

Let's just say Thirty-something :-)

2. Which country do you live in?

The United Kingdom.

3. Do you play locally, on a server or both?

Usually locally. I've tried playing on CAO, but the intricacies of setting up terminal options so that they're just so discourages me a bit.

I do like to be the voyeur and watch others playing though.

4. Do you play Tiles or ASCII or both?

Both. I would love the full screen mouse-tile interface to work with an ASCII version though. ASCII tiles perhaps? (hinthint)

5. Which Operating Systems do you use at home?

Primarily Windows (Vista, urgh...it came with the laptop, its not a patch on XP). I do have it dual-installed with Ubuntu which I do occasionally use (I was a former UNIX monkey. And before you ask, XEmacs).

6. Which Roguelikes have you played before? (Nethack, ADOM, Angbandetc.)

Of the big ones (and in order of time spent playing in descending order): Crawl, TOME, Angband, Unangband, Sangband, ADOM, Rogue.

And lots and lots of the minor ones.

You'll notice no Nethack. That's deliberate. I'm also especially crap at ADOM.

7. Where did you learn about Crawl?

I think it was actually r.g.r.m.

8. And when?

A long time ago, but not that long ago - I think it was just after Stone Soup came out and I had gotten back into roguelikes after a long time away. I should really play some b26 as well to see what that feels like in comparison to SS.

9. How often did you win Crawl? (If none, you may specify your best game.)

Win? You're having a laugh! My best was a 2-rune MDFi 19 that died due to "let's-die-from carelessness-for-no-good-reason-at-all-because-I-didn't-quite-have-my-Crawl-head-after-shortly-commencing-another-session-of-play-the-next-day" syndrome.

10. If you take part in the Crawl tournament: where did you hear about it?

I don't take part mainly due to lack of time and my own general crapness at Crawl.

11. Did you ever recommend Crawl to someone?

Yes, several. Including my other half, who has recently given up playing due to Orc Priests getting nerfed.

12. Which computer game played most in the last month (July)?

In July? Hardly any. Though I did reinstall and subsequentlydelete Civilisation 4 for what seems like the umpteenth time.

Big or small levels?

Big levels or smaller levels? is a question that has come up in the long-running Exploitation and Evasion thread over on r.g.r.d.

So...should Kharne keep its current (IMHO) big levels or move to smaller levels?

(if it helps the discussion, I intend for the shortest path through the dungeon to be forty levels, split over four dungeon branches, and for the maximum character experience level to be XL20).

Wednesday 19 August 2009

On rehabilitating old roguelikes...

Krice appears to have given up his attempts at taking over and restarting development of Abura Tan. Its a shame, as it showed a lot of potential.

Another two old abandoned roguelikes that seemed full of potential were Nethermost Wanderings and The Legend of Saladir.


Both of these have not been updated for a long time (Saladir since 1998, NW since 2005.) If I wasn't so involved with a part-time degree, coding Kharne, and occasionally dabbling in C#, I'd be very keen on taking over one of these projects.

Of course, the problem with Saladir was that, as far as I'm aware, the source code was never released. Perhaps there's a lesson to be learned there?

The code for Nethermost Wanderings on the other hand, is freely available. Perhaps someone else out there is masochistic enough to try some new development? (although it doesn't say anything on the website, when you look at the current CVS source, it has actually been GPLed)

Mind you, I hope in years to come people don't talk about Kharne in the same way...but at least by releasing the source code, if something does happen, at least it has a chance of living on.

Saturday 15 August 2009

Refactoring Progress #2

Ironically, one of the most irritating things about my existing code is the tendancy for it to have crept over 80 characters in length (on each line). According to the Object Pascal Style Guide (and to common sense), lines longer than 80 characters are verboten.

I'm still working on the UnitItem unit which contains the definitions for the TItemEnchantment, TItemArchetype and the TItem classes (which all do exactly what they say on the tin). Its taking longer than I thought because, frankly, the code is a mess. But already, I'm starting to see the difference both in terms of understanding and readibility, and even performance has improved slightly.

Friday 14 August 2009

More on Item Effects

One of the things I've found whilst doing the refactoring is some code I wrote which allows suitable messages to be passed to the message log when you wear or remove items to reflect the properties of said items. Until now it has never been called or used.

For example, consider an amulet that grants +alertness and +reslifedraing.

This would be the display in the message log when you wear-identify it:

You see an Ornate Copper Amulet here
You pick up an Ornate Copper Amulet
You now have an Ornate Copper Amulet
You put on an Ornate Copper Amulet
You feel more resistant to lifedraining
You feel more alert
It is an Copper Amulet of Being!
You take off an Copper Amulet of Being
You feel less resistant to lifedraining
You feel less alert

I personally like this, though I'm a bit worried that it may clutter up the message log a bit too much. What do you think?

Thursday 13 August 2009

Refactoring Progress #1

Here's the current progress in the great Refactor/Tidyup/Open Source project. Red lines represent Units/Forms that have been completed.

(yes, that is the View->Project Manager dialog for the main Kharne executable)

Here's an example of an unrefactored procedure in all its hideousness (including, you'll note, no comments, the shame):

function TMonster.GetSinglePrefix: string;
begin
if (Length(Trim(UniqueName)) <> 0) then Result := ' ' else
begin
if (MonsterName[1] in ['A', 'E', 'I', 'O', 'U', 'a',
'i', 'e', 'o', 'u']) then

Result := 'an '
else
Result := 'a ';
end;
end;

And here is the refactored version:

{ Return the singular form of the prefix }
function TMonster.GetSinglePrefix: string;
begin
{ Logging }
hLog.Add('{now} {lNum} TMonster.GetSinglePrefix()');

{ Default result }
Result := '';

try
{ Again, Uniques do not have prefixes }
if (FUnique) then
Result := ' '
else
begin
{ Deal with vowels }
if (Vowel(FName)) then
Result := 'an '
else
Result := 'a ';
end;
except
{ in case of error, log the Exception }
on E: Exception do hLog.AddException(E);
end;
end;

Its still not perfect, there are still many optimisations that could be done (e.g. raising exceptions properly, dealing with those spaces that keep appearing in many of my punctuation routines), but the second excerpt I'd be happy to see released into the world with my name on it.

Wednesday 12 August 2009

Refactoring and the MPL

As I mentioned a while back, Kharne will soon be going open source, under the Mozilla Public License. My reasons for doing this aren't born out of any great philosophical attachment to the Open Source Paradigm (whilst I do have an Ubuntu partition on my laptop that I do occasionally boot into, I spend most of my time in Microsoft Windows) but rather for more prosaic reasons:
  • I don't want Kharne to die through any hypothetical future lack of involvement on my own part
  • I welcome the scrutiny that other people reading and checking my code brings
  • I want there to be at least one Roguelike out there written in Delphi
The last point should not be taken as an adoption of language advocacy, though - I've long held the position that the best language is the one that gets the job done as quickly as possible with as few errors as possible meeting as many of the requirements as possible (though a roguelike developerd in COBOL would be rather amusing).

However, releasing something as Open Source doesn't just mean tacking the appropriate headers onto the top of each unit of source code. For better or for ill, the current Kharne source code is very much like the proverbial Curate's Egg. Some of it is spellbindingly excellent and some of it is just plain awful. So prior to release of the code, I'll engage is a massive code refactor and tidyup.

This refactor is something I've already started. But what does this it entail?
  • General Code Tidyup and Commenting
  • Adherence to Coding Standards
  • Removing Redundant Units
  • Proper Encapsulation of all Classes
  • Abstraction of Procedural and non-Class code into Classes (where possible)
  • Logging and Error Handling
Additionally in this exercise, I'd also like to do prepare the ground for a possible future multiplatform port (or at least remove the most gregarious incompatibilities with Lazarus).

Now all this will take time, but I don't expect there to be much delay in the release of future releases, if any at all (didn't expect me to say that, did you?). The pain now will be worth the gain in the future.

Tuesday 11 August 2009

Logging and Locking up Issues

I've noticed that occasionally Kharne locks up, which I think is due to an infinite loop condition somewhere. I've been unsuccessful in tracking down the exact problem, so to aid finding and correcting this problem, the next version will have logging added. I'm using Oliver Touzot's THotLog component which as well as being freeware, has all the functionality I need.

Not only do I intend to log errors and exceptions, I also intend to log, as much as possible, the complete execution of the program as well.

So far, I've only converted a couple of source code units to use Logging, but there is some sample output below. This will be written to a log file in the same directory as KHARNE.EXE.

2009-08-11 00:01:44 1 TVault.Create(1, 'Round')
2009-08-11 00:01:44 2 TVault.Create(2, 'Octagon')
2009-08-11 00:01:44 3 TVault.Create(3, 'Square')
2009-08-11 00:01:44 4 TVault.Create(4, 'Open')
2009-08-11 00:01:44 5 TVault.Create(5, 'Lesser Cell')
2009-08-11 00:01:44 6 TVault.Create(6, 'Left Diagonal')
2009-08-11 00:01:44 7 TVault.Create(7, 'Right Diagonal')
2009-08-11 00:01:44 8 TVault.Create(8, 'Octagon')
2009-08-11 00:01:44 9 TVault.Create(9, 'Rooms')
2009-08-11 00:01:44 10 TVault.Create(10, 'Central')
2009-08-11 00:01:44 11 TVault.Create(11, 'Intermeshed')
2009-08-11 00:01:44 12 TVault.Create(12, 'Turns')
2009-08-11 00:01:44 13 TVault.Create(13, 'Tiny')
2009-08-11 00:01:45 14 TVault.Create(14, 'Small Entry')
2009-08-11 00:01:45 15 TVault.Create(15, 'Small')
2009-08-11 00:01:45 16 TVault.Create(16, 'Long Pit')
2009-08-11 00:01:45 17 TVault.Create(17, 'Entry')
2009-08-11 00:01:51 18 UnitVault.GetRandomVault(0, 0)
2009-08-11 00:01:51 19 TVault.GetVaultMap()
2009-08-11 00:01:51 20 UnitVault.GetEntryVault()
2009-08-11 00:01:51 21 TVault.GetVaultMap()

Adding logging such as this is another task I need to do before I MPL the source code.

Monday 10 August 2009

Sunday 9 August 2009

Some thoughts on Implementation of Potions

Looking back at the roadmap, the two major tasks left to do in this release cycle are potions/scrolls and corpses.

In this post I'll be talking about potions in particular. I've already defined them previously, but I want to talk more about their implementation. (Incidentally Kharne will follow the usual Roguelike concepts with potions for the time being. Eventually, I do want to allow them to be used also in some sort of Alchemy-based crafting system, much like that is found in some MMORPGs)

So, to begin with, these are the potions I intend to implement for the next release:
  • Potion of Healing: Andrew Doull of Unangband fame (and much more besides) commented a while back on the role of healing in Angband. I don't intend to deviate from his analysis too much, but I propose that standard Potions of Healing (which should be the commonest potion type found) shouild heal a flat 50% of lost HP instantly.
  • Potion of Extra Healing: Like its lesser brother, this will restore HP. instantly I'm minded to make this restore 100% of lost HP. Unlike the original Rogue, I'm not intending to allow maximum HP to be increased by chugging Potions at maximum health.
  • Potion of Curing: In Crawl, healing potions cure effects like poison and sickness, but I'm not keen on that usage as I feel it leads to using up valuable potions. I think the alternative method of providing a specialised potion to cure negative status effects would be useful instead (if it turns out that this is a bad idea, I'd be willing to change healing potions to work like Crawl's)
  • Potion of Regeneration: This was proposed by Andrew in the post mentioned above, and I'm also intending to implement it in Kharne. This should work like Crawl's Trog's Hand. For X turns, you regenerate 10% of maximum health back a turn.
  • Potion of Speed: For X turns, the player can move and act at 3 times his/her normal speed.
  • Potion of Restore Abilities: This potion will remove any negative status effects upon the player's being (e.g. if they are drained of strength by a creature, like Shadows used to do back in the days of AD&D). (This will require some additional coding for the class that holds the player to include a temporary buffer pool for each stat - it is this buffer pool that gets adjusted for temporary effects like strength drain and NOT the actual player's inherent strength).
  • Potion of Confusion: This will mimic the standard Confusion effect found in many Roguelikes, and lasts for X turns. This is actually very easy to implement, as it just requires a check on keypress to see if a movement key has been pressed, and if so, randomly choose another direction instread)
  • Potion of Blindness: Again, another potion with unbeneficial side effects that lasts for X turns. Technically speaking, this is just a matter of reducing the effective FOV radius to 0 squares (instead of the standard 7).
  • Potion of Paralysis: Paralysis will prevent you from acting. (I also intend it to be a monster attack posessed by Ghouls). Again lasts for X turns.
  • Potion of Free Action: Whilst under the effects of this potion, you cannot be paralysed or slowed. Another potion with a duration of X turns.
  • Potion of Combat Mastery: Massively increase the player's chance to hit for a certain number of turns. To balance this out, I feel the duration should not be as long as other potions, perhaps only 10-20 turns.
  • Potion of Reflexes: A defensive as opposed to an offensive potion. I intend for this to boost evasion for a limited period. Evasion is the stat used to determine if an attack hits or not, and is affected by armour worn, dexterity and other factors. Unlike Armour Class, its actually a logarithmic stat.
  • Potion of Might: Massively increase the player's damage for a certain number of turns. Like Potions of Combat Master, I feel the duration should not be as long as other potions, perhaps only 10 turns maximum.
So, in summary, there will be four potions with instant action-effects (Potions of Healing, Extra Healing, Curing and Restore Abilities), six that provide some sort of boost over time (Potions of Regeneration, Speed, Free Action, Combat Mastery, Reflexes and Speed) and three harmful potions (Blindness, Confusion and Paralysis).

In terms of distribution of potions, the most common (for obvious reasons) have to be the healing potions, and I feel that the harmful potions should account for no more than 20% of all potions found.

Thoughts?

Saturday 8 August 2009

Implementing Transient Events - an update

After some feedback, I've made a few tweaks to the TGameTimer class. Here is the new Tick method:
{ Decrease the duration of the timer by a turn }
function TGameTimer.Tick: Boolean;
begin
{ Trigger events if they are defined }
if (FTimerDurationLeft > 0) then
begin
{ Interval Event }
if (Assigned(FTimerTickEvent)) then
FTimerTickEvent(Self);
end
else if (FTimerDurationLeft = 0) then
begin
{ End Event }
if (Assigned(FTimerEndEvent)) then
FTimerEndEvent(Self);
end;

{ Decrement the Counter }
Dec(FTimerDurationLeft);

{ Return true if the Timer is still active }
Result := GetStatus;
end;
Moving the decrement of the tick timer to after the tick events means that it now correctly works with a duration of one turn - in these circumstances previously only the Start and End Events would be fired.

The interface to the Event Hooks themselves have changed

{ Procedure Pointer for Event Hook }
type TGameTimerEvent = procedure(const GameEvent: TObject);

This allows passing in of the triggering TGameTimer object (referenced by Self in the tick method, and subsequently cast to TGameTimer in the actual hook).

For those of you familiar with Delphi this is pretty much (with a few minor differences) what TNotifyEvent does.

I've also removed the Progress property entirely. Since the Tick event is designed to be called every game turn then its simply a matter of comparing FTimerDuration and FTimerDurationLeft as needed there instead.

Friday 7 August 2009

Kharne 0.03c Available

v0.03c is now available.

(edit: see the addendum at the bottom of this post if you downloaded this version before midnight GMT on the 7th)

Although this version doesn't have the promised potions and scrolls implemented, the timing functionality is now included and I hope to release the next version soon.

Here are the changes in this version:
  • Added milestones to the Character Log.
  • Multiple Items can now exist on the same square.
  • Firing a ranged combat weapon now correctly updates the turn count.
  • The maximum damage a monster can do to a player in a single hit is now (Maximum Hit Points - 1).
  • Piles of Silver and Bronze can now be found throughout the dungeons.
  • Items have been reduced in cost by a factor of five.
  • There is now a wider variety of entry vaults.
  • Amulets can now be found in fountains.
  • Combat Text is now much more descriptive.
  • Monster attacks that hit you and cause damage are now displayed in a different colour.
  • Co-ordinates are now only displayed if the Wizard Window is displayed.
  • The number of hit points and magic points gained per level has been slightly increased.
  • The effect on speed of carrying weights greater than maximum load has been reduced slightly.
  • Monsters will use ranged attacks less often.
Addendum: I've had to reupload the v0.03c archive as the version I originally uploaded had two major bugs in it: the alertness radius (i.e. the radius of the FoV) was set to half what it should have been, and some of the entry vaults would have been generated without an up set of stairs. Please accept my apologies and redownload and overwrite the previous installation (the two files that have actually changed are Kharne.exe and Kharne.db3)

Thursday 6 August 2009

Implementing Transient Events: An example

This is a video of a simple usage of the TGameTimer class - to increase and decrease the FOV of the Player. This takes place using an event of 30 turns duration.

The default radius of the FOV for this demo is 3 squares (normally it is 7), and at the start of the event, this is increased to 9. Half-way through (15 turns later), this is reduced to 6, and then 15 turns after that, the radius is reduced to 3. Appropriate messages are displayed in the messagelog.

I'll go through the code required to produce this in my next post.



It is worthwhile to note that this means that I can now implement variable stage poisons and sickness, like Crawl posseses. But I have many ideas for Kharne beyond even what Crawl implements.

Implementing Transient Events: The TGameTimer class

One of the most difficult tasks in programming a roguelike is to handle transient events, such as poison, strength buffs and so on. As well as the effects (lose a HP! gain strength!), you've got to keep track of the duration, and deal with cancelling the events. One (obviously unsatiisfactory) way of implementing this is to hard code it The other, is to use a class to handle such events.

Hence the TGameTimer class.

I've made the full source code available here and here, and also listed it below (you may need to copy and past to get around wordwrapping issues). Although written in Delphi, it should compile in FreePascal without too many changes (if any). I'm releasing it as public domain, so feel free to use and abuse it as you see fit.

Basically, the class uses good ole' Procedure Pointers to allow (optional) event hooks to occur in three scenarios:

  • Upon Event Creation
  • Upon an Event Tick
  • Upon Event Expiry

The constructor of the object takes an event type and an event duration in turns. as well as the event hooks themselves.

If anyone wants a demo app showing the class in action, let me know and I'll post one. Otherwise, you'll have to wait until the next major release of Kharne, when I intend to use TGameTimer to implement potion functionaility (finally!).

I'll do a followup post over the weekend with some additional comments, and thoughts on how it can be improved. In the meantime, comments are most welcome.


{ Timer Handling

A TGameTimer represents a transient event that has a duration.

An example would be the side-effects of drinking a Potion of Might - it increases
strength for a limited number of turns. We optionally define a number of procedure
pointers to point to events that occur at the beginning and end of the duration,
and also on every turn.

For example, upon drinking a Potion of Might, the event might display a message
stating you feel mighty (as well as increasing your strength). As the duration
of the effects decrease, further messages will be displayed stating that the
effects of the potion are wearing off, and then when the duration has expired,
your strength reverts back to normal

To implement this, use the follwing steps as a guide:

1. Set up a variable:

DrinkMightPotionEvent: TGameTimer;

2. Define three events as follows:

procedure MightPotionDrink(const Turns: Integer = 0);
procedure MightPotionTick(const Turns: Integer = 0);
procedure MightPotionEnd(const Turns: Integer = 0);

procedure MightPotionDrink(const Turns: Integer);
begin
DisplayMessage('You feel mighty!');
Player.Strength := Player.Strength + 10;
end;

procedure MightPotionTick(const Turns: Integer);
begin
if (DrinkMightPotionEvent.Progress = 50) then
DisplayMessage('The effects are wearing off!');
Player.Strength := Player.Strength - 5;
end;

procedure MightPotionEnd(const Turns: Integer);
begin
DisplayMessage('You no longer feel so mighty!');
Player.Strength := Player.Strength - 5;
end;

3. Instantiate the event:

DrinkMightPotionEvent := TGameTimer.Create(timMight,
DURATION_MIGHT_POTION,
MightPotionTick,
MightPotionDrink,
MightPotionEnd);

4. Then, on every turn that passes, simply call

if (Assigned(DrinkMightPotionEvent)) then DrinkMightPotionEvent.Tick;

}

{ Define the types of timers as an enum for simplicity }
type TGameTimerType = (timSpeed,
timConfusion,
timBlindness,
timSeeInvisible,
timParalysis,
timFreeAction,
timCombatMastery,
timReflexes,
timMight);

{ Procedure Pointer for Event Hook }
type TGameTimerEvent = procedure(const Turns: Integer = 0);

{ Class Definition - it inherits the ICommonObject interface to gain access
to the StringValue method to allow easy persistance }
type TGameTimer = class(ICommonObject)
private
FTimerType: TGameTimerType; // Timer Type, from the enum previously defined
FTimerDuration: Integer; // Starting Duration, in turns
FTimerDurationLeft: Integer; // Duration Left, in turns
FTimerTickEvent: TGameTimerEvent; // Optional Event to call on each decrement
FTimerStartEvent: TGameTimerEvent; // Optional Event to call on starting the timer
FTimerEndEvent: TGameTimerEvent; // Optional Event to call on ending the timer (i.e. turns left = 0)

{ Private functions to support class properties defined below }
function GetStatus: Boolean;
function GetProgress: Integer;

public
{ Standard Constructor }
constructor Create(TimerType: TGameTimerType;
TimerDuration: Integer;
TimerTickEvent: TGameTimerEvent = nil;
TimerStartEvent: TGameTimerEvent = nil;
TimerEndEvent: TGameTimerEvent = nil);

{ Decrement the Timer by one turn. Will return true if the timer has not expired }
function Tick: Boolean;

{ Interface Method for Persistance }
function GetStringValue: String;

{ Properties }
property TimerType: TGameTimerType read FTimerType; // Return the enum
property TimerDuration: Integer read FTimerDurationLeft; // Number of Turns left
property TimerProgress: Integer read GetProgress; // Number of Turns left as a percantage (0-100) of the original duration
property Active: Boolean read GetStatus; // True if Number of Turns left is > 0
end;


implementation

{ Standard Constructor - this is deliberately the only way to set up the duration etc }
constructor TGameTimer.Create(TimerType: TGameTimerType;
TimerDuration: Integer;
TimerTickEvent: TGameTimerEvent;
TimerStartEvent: TGameTimerEvent;
TimerEndEvent: TGameTimerEvent);
begin
{ Load the private member data }
FTimerType := TimerType;
FTimerDuration := TimerDuration;
FTimerDurationLeft := TimerDuration;
FTimerTickEvent := TimerTickEvent;
FTimerStartEvent := TimerStartEvent;
FTimerEndEvent := TimerEndEvent;

{ If we have a start event defined then execute it now }
if (Assigned(FTimerStartEvent)) then
FTimerStartEvent(FTimerDurationLeft);
end;

{ Return true if the timer hasn't expired }
function TGameTimer.GetStatus: Boolean;
begin
Result := FTimerDurationLeft > 0;
end;

{ Decrease the duration of the timer by a turn }
function TGameTimer.Tick: Boolean;
begin
Dec(FTimerDurationLeft);

{ Trigger events if they are defined }
if (FTimerDurationLeft > 0) then
begin
{ Interval Event }
if (Assigned(FTimerTickEvent)) then
FTimerTickEvent(FTimerDurationLeft);
end
else if (FTimerDurationLeft = 0) then
begin
{ End Event }
if (Assigned(FTimerEndEvent)) then
FTimerEndEvent(FTimerDurationLeft);
end;

{ Return true if the Timer is still active }
Result := GetStatus;
end;

{ Returns the number of Turns left as a percantage (0-100) of the original duration }
function TGameTimer.GetProgress: Integer;
var
Percentage: Double;
begin
Percentage := FTimerDurationLeft / FTimerDuration;
Result := Trunc(Percentage * 100);
end;

{ Get the Timer as a String }
function TGameTimer.GetStringValue: String;
begin
{ TODO: We will need to extend this to allow hashing of the attached procedures }
Result := Format('%d%d', [Ord(FTimerType), FTimerDuration]);
end;

Flavoured Yummy Combat

Up until now, combat in Kharne has been pretty boring. You hit the monster. The monster hits you. You hit the monster back. And so on. Until one of you drops.

But no longer.

Ogres bash, slam and smash. Bees sting. Wraiths claw you. Hounds bite and claw. And Minotaurs gore you.

Wednesday 5 August 2009

Sprucing up Monster AI #1

Monster behaviour in Kharne is currently pretty dumb. I'm not talking about (the not-implemented) special attacks and behaviour for each creature, but rather they will currently stand and fight even though the player is butchering them.

I've been thinking about this and I can achieve what's known in software engineering as a 'quick win' which will enchance AI considerably.

Put simply:

Allow certain monsters to flee away from the player if they are sufficiently injured. If there are other monsters visible that belong to the same ecology, then flee towards them. Else flee directly away from the player. If a fleeing monster cannot move away from the player, then it turns and fights.

Print fleeing/unfleeing messages as appropriate ("the wibbly flees!", "the wibbly turns and fights!")

Now, there is some additional subtlety I intend to code. If there is a Unique Monster within line of sight, injured monsters don't flee but instead shout something along the lines of "We will die for you, Wibbly the Doomy!"

Perhaps the opposite effect could also occur - when a Unique dies, all sentient monsters within view flee with a "Seeing its Champion dead, the wibbly runs away". Obviously this would not be a permanent fleeing, but of time-limited duration.

(cross-posted at r.g.r.d.)

Tuesday 4 August 2009

Spicing up ASCII: Combat

I was browsing through the archives of r.g.r.d. when I came across this thread about indicating attack results within an ASCII paradigm.

I've had a similar idea in mind for Kharne for a while now. To summarise:
  • if you take damage, the character's symbol (@ of course) flashes red briefly. How deep a red depends upon how much damage has been taken.
  • if you inflict damage, the symbol of the monster that has taken damage flashes red briefly. This is not graduated.
  • if a monster attacks you, its symbol changes briefly to a green colour.
  • if a monster has a status effect, for example, held, paralysed, sleeping, poisoned, then its ascii character is shaded a different colour.
Thoughts?

Flickr & New Links

I've created a Flickr Photostream (accessible on the right-hand side) to hold screenshots and images of interest. I'll be adding more frequently. I've also added a few more links to the blogroll. If you want a link to your roguelike site added, just drop a comment here on this blog, or contact me in the #rgrd chatroom, where I am known as starbog.

Monday 3 August 2009

"Yes, it was just as well I had wizard mode on..."

These sorts of places will only get harder once I give the monsters special behaviour and attacks.

Sunday 2 August 2009

Preview: Character Milestones

Every other roguelike has them, so Kharne now has them too (or will do, in the next version): Character milestones. What is a milestone? Here's an example from Crawl:

15328 | Lair:9 | Identified the +2 helmet of Intuition (You bought it in a shop on level 9 of the Lair of Beasts)
15332 | Lair:9 | Identified the cursed -2 pair of boots of Xacriyts (You bought it in a shop on level 9 of the Lair of Beasts)
15336 | Lair:9 | Identified the +2 cloak of Unreason (You bought it in a shop on level 9 of the Lair of Beasts)
15882 | Lair:10 | Entered Level 10 of the Lair of Beasts
16246 | Lair:10 | Reached skill 16 in Axes
16600 | Slime:1 | Entered Level 1 of the Pits of Slime
16883 | Snake:1 | Entered Level 1 of the Snake Pit
16958 | Snake:1 | Reached XP level 13. HP: 86/118 MP: 8/8
17470 | Snake:1 | Noticed Rupert
17495 | Snake:1 | Defeated Rupert
18143 | Snake:2 | Reached skill 10 in Armour
18393 | Snake:2 | Reached skill 17 in Axes
18409 | Snake:2 | Received a gift from Trog
20544 | Snake:5 | Entered Level 5 of the Snake Pit
20837 | Snake:5 | Reached XP level 14. HP: 128/128 MP: 9/9
21258 | Snake:5 | Reached skill 1 in Spellcasting
21683 | Snake:5 | Reached skill 18 in Axes
21694 | Snake:5 | HP: 10/256 [naga warrior/uncursed club (6)]
21698 | Snake:5 | HP: 4/128 [poison]
22626 | Snake:4 | HP: 6/128 [poison]
22631 | Snake:4 | HP: 4/128 [poison]
22636 | Snake:4 | HP: 5/128 [poison]
24007 | Snake:5 | Got a serpentine rune of Zot
25141 | Swamp:5 | Entered Level 5 of the Swamp
25278 | Swamp:5 | Reached skill 19 in Axes
25522 | Swamp:5 | Reached XP level 15. HP: 64/138 MP: 12/12
25817 | Swamp:5 | Got a decaying rune of Zot


So far, I have:
  • Starting out
  • Dying
  • Gaining an XP level
  • Gaining rank 5/10/15 etc in a skill
  • Noticing a unique monster
  • Killing a unique monster
  • Finding an artifact
Here's an except from a Kharne log:

Turn Place Note
--------------------------------------------------------------
0000191 The Wilderlands 1 Identified The ancient artifact "Ethaedric"
0000396 The Wilderlands 1 Noticed Adrialith the Wrathful
0000409 The Wilderlands 1 Killed Adrialith the Wrathful
0000409 The Wilderlands 1 Reached Level 2, HP: 11/11, MP: 21/21
0000571 The Wilderlands 2 Explored Level 2 of The Wilderlands
0000709 The Wilderlands 2 Noticed Etib the Tormentor
0000709 The Wilderlands 2 Killed Etib the Tormentor
0000814 The Wilderlands 3 Explored Level 3 of The Wilderlands
0000841 The Wilderlands 4 Explored Level 4 of The Wilderlands
0000933 The Wilderlands 4 Reached Level 3, HP: 15/15, MP: 32/32
0000938 The Wilderlands 5 Explored Level 5 of The Wilderlands
0001119 The Wilderlands 6 Explored Level 6 of The Wilderlands
0001384 The Wilderlands 6 Gained Skill Level 5 in Heavy Armour
0001386 The Wilderlands 6 Reached Level 4, HP: 19/19, MP: 43/43
0001672 The Wilderlands 7 Explored Level 7 of The Wilderlands
0002151 The Wilderlands 7 Killed by a Desperate Adventurer


I think it looks pretty sparse, so far. So...what character actions are worthy of milestones?

Saturday 1 August 2009

Representing graphically multiple items on the same tile

Following discussion here and here, I'd like your thoughts on using a superscript '+' to represent graphically the presence of multiple items on the same tile: