Showing posts with label Potions. Show all posts
Showing posts with label Potions. Show all posts

Monday, 4 July 2011

Upcoming Bug Fixes

I've identified the following issues with potions and scrolls, which I will be fixing in a bug-fix version (0.06a) which hopefully will be released soon (in the next couple of days):
  • Some potions aren't identified after use when they should be.
  • New occurrences of some potions that have been previously identified are thereafter wrongly generated as unidentified.
  • The text to indicate that a Potion of Combat Mastery's effects have ended is wrong.
  • Rage potions are not named properly.
  • Enchant Weapon scrolls shouldn't enchant a wielded shield.
  • Some scrolls aren't being identified after use properly.
  • When raging, and trying to consume potions, even though you get the "you can't do that right now", the potion is still consumed.
Apologies if any of these bugs have disrupted gameplay.

Monday, 27 June 2011

Potions completed

Potions have almost all been implemented, just debugging them all now. Here's a potion of Rage (which increases your Strength dramatically for a short period of time) in action:


Once they are done, I think I will release v0.06 - almost a year after the last version. Its been too fucking long...

Friday, 24 June 2011

Potions

Potions are a pain in the arse to implement. Not so much the instant effect ones like Healing, but time-limited ones like Might/Confusion and so on. I was tempted to hack in quick counter attributes for each potion on each PC class, but I should really do the full object-orientated method outlined here instead.

That said, I like Mario's suggestion in the comments of just using an array of counters, and it would mean actually getting code out quicker.

Monday, 12 April 2010

Q is for....

Whilst working on potions, a thought just occured to me.

Press Q to (q)uaff a potion.

But....

Press Q to (q)uaff from a fountain.

What happens if you're standing on a fountain square and want to (q) uaff a potion instead?

Saturday, 10 April 2010

Progress Report #19

The next version will be out soon (yes, really!)

Here's the current intended feature list. I'm still working on finishing off the implementation of potions.
  • Potions have been implemented.
  • Some monsters now follow characters up and down stairs.
  • Only the just finished game will be now highlighted correctly in the hiscore list after character death.
  • Spurious double quotes (") will no longer appear in automatically generated character names.
  • When resting, there is chance of a monster appearing randomly near the character.
  • Eating now restores all lost health points.
  • When starting a 2nd game in the same session, the Character Background will be reset properly.
  • On Character death, the Main Window Title will be reset properly.
  • Elemental Intrusion Creatures no longer have as many hitpoints.
  • A Character Knowledge screen has been added that shows identified and unidentified items. This can be accessed by pressing "/".
  • Identified items that are restocked in shops are now pre-identified correctly.
  • A keyboard shortcut ("^") has been implemented to refer to the last item picked up.
  • Unidentified Scrolls are now identified properly on Character Death.
  • A Toggle ("W") has been added to the inventory screen that displays the weights of items.
And, yes, it will be still be in Delphi. There's too much done and to do now to start rewriting it in another language.

Wednesday, 14 October 2009

Scrolls and Potions oh my

Now that the first tranche of scrolls have been implemented, and my exams are finally over for the year, the next thing to add to the game are potions. Potions are slightly more complicated to implement than scrolls, given that scrolls are one-off effects and potions have effects over time. A while back, I listed the various types of potions I'd like to implement, but as with scrolls, I'll probably only implement a subset of these at first, and add more in later. I will release 0.0.5c when I have potions implemented.

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.

Thursday, 6 August 2009

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;

Tuesday, 14 July 2009

Potions and Scrolls

I've just started implementing the use of potions and scrolls. They will follow the usual roguelike convention, i.e. they start off scrambled and can have a "{tried}" status if they produce no obvious effects. The list of potions and scrolls I intend to implement at first is given here.

One additional piece of functionality which I'd like to implement but will probably not for the time being is adding the standard roguelike shortcut of (q)uaffing an item by assigning keyboard shortcuts on the inventory (as opposed to the current method of dragging the item onto the small icon (shown above)). This will require some refactoring.