Hey all, I wrote the class before to handle transient effects (like drinking a potion of might) in Kharne. It was a pain to grasp and define the concepts, so I'm guessing others may have problems with it as well. Written in Delphi, but should compile in FPC/Lazarus as well without too many changes. (apart from defining an ICommonObject interface with a getstringvalue method of course)
Its in the public domain so knock yourself out with it.
unit UnitTimers;
interface
uses SysUtils;
{ 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;
end.
Here's a noddy Delphi app (with two buttons and a memo on a form) as an example:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, UnitTimers, StdCtrls;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
Event: TGameTimer;
public
{ Public declarations }
end;
procedure EventStart(const Turns: Integer = 0);
procedure EventTick(const Turns: Integer = 0);
procedure EventEnd(const Turns: Integer = 0);
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure EventEnd(const Turns: Integer);
begin
Form1.Memo1.Lines.Add('End ' + IntToStr(Turns) + ' Turns Left');
end;
procedure EventStart(const Turns: Integer);
begin
Form1.Memo1.Lines.Add('Start ' + IntToStr(Turns) + ' Turns Left');
end;
procedure EventTick(const Turns: Integer);
begin
Form1.Memo1.Lines.Add('Tick ' + IntToStr(Turns) + ' Turns Left');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Event := TGameTimer.Create(timSpeed,
20,
EventTick,
EventStart,
EventEnd);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if (Assigned(Event)) then Event.Tick;
end;
end.