Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - Perdurabo

Pages: [1]
1
Using Libtcod/SDL/Code::Blocks/Mingw64/GCC4.8.1 on Win7/64bit

So I decided to switch over to using a 64-bit version of my toolchain for my roguelike in development, as well as moving to C++11, and I encountered a lot of hassle in doing so. So I wrote up some instructions on how to do it. I will at some point put all this up on a webpage somewhere with relevant screenshots.

Let me know if I've missed anything or screwed anything up, or just let me know your thoughts.

1. First, download and install the 64-bit MinGW-builds version of MinGW at: http://sourceforge.net/projects/mingwbuilds/files/?source=navbar
You want the Web Installer labelled "Download mingw-builds-install.exe (170.0 kB)".
Install this in somewhere like "C:\MINGW64". Please don't use any spaces in the path, as CodeBlocks can't handle spaces very well in library filepaths.
When you are installing, please select the following options:

64-bit,
POSIX threads (to enable C++11 std::threads)
SJLJ debugging (not important AFAIK)


2. When this is done, add c:\MINGW64\bin to the system path (Computer/Properties/Advanced System Settings/Environment Variables). Note when you apply the change, you will have to restart any command prompts, programs etc as the PATH only is read at their startup.

3. Now set up and configure Codeblocks following the instructions given in:
http://stackoverflow.com/questions/16716454/setting-up-mingw-and-codeblocks-in-windows-8-64-bit

There are a few minor differences however, mainly seemingly in the layout of the latest Mingw (in the Search directiories settings). Here are my settings:

Global Compiler Settings/Toolchain Executibles/Program Files

Compiler's Installation Directory: C:\Mingw64
C Compiler: x86_64-w64-mingw32-gcc.exe (found in C:\Mingw64\bin)
C++ Compiler: x86_64-w64-mingw32-g++.exe (found in C:\Mingw64\bin)
Linker for Dynamic Libs: x86_64-w64-mingw32-g++.exe (found in C:\Mingw64\bin)
Linker for Static Libs: x86_64-w64-mingw32-gcc-ar.exe (found in C:\Mingw64\bin)
Resource Compiler: windres.exe (found in C:\Mingw64\bin)
Make Program: mingw32-make.exe (found in C:\Mingw64\bin)


Global Compiler Settings/Toolchain Executibles/Additional Paths

Add C:\Mingw64\libexec\gcc\x86_64-w64-mingw32\4.8.1 here

Global Compiler Settings/Search Directories/Compiler

Add the following:

C:\mingw64\include
C:\mingw64\x86_64-w64-mingw32\include
C:\Mingw64\lib\gcc\x86_64-w64-mingw32\4.8.1\include\c++
C:\Mingw64\lib\gcc\x86_64-w64-mingw32\4.8.1\include\c++\backward
C:\Mingw64\lib\gcc\x86_64-w64-mingw32\4.8.1\include\c++\x86_64-w64-mingw32
C:\Mingw64\lib\gcc\x86_64-w64-mingw32\4.8.1\include


Global Compiler Settings/Search Directories/Linker

Add the following:

C:\Mingw64\Lib
C:\Mingw64\x86_64-w64-mingw32\lib


Global Compiler Settings/Compiler Settings/Other options

Add "-m64"

Now save everything, making sure to "Auto-detect" first. Best to restart code blocks.

4. Now download a 64-bit version of SDL. This can be found at

http://sourceforge.net/projects/mingw-w64/files/External%20binary%20packages%20%28Win64%20hosted%29/SDL/

You want the file "SDL-1.2-20111107-win64.tar.gz"

Extract it to something like C:\SDL so the path is C:\SDL\SDL-1.2-20111107-win64 (for example).

Don't do anything else with this at the moment.

5. Now following the instructions at

http://doryen.eptalys.net/data/libtcod/doc/1.5.2/html2/compile_libtcod_mingw.html?c=true&cpp=true&cs=true&py=true&lua=true

down to, but not including, "Compiling libtcod" download the latest Libtcod code to somewhere like C:\libtcod. Don't compile anything yet, we have further changes to do.

5a. Note you may need to download and install MSYS (the version you want is MSYS-1.0.11.exe http://sourceforge.net/projects/mingw/files/?source=navbar) if you haven't it already to follow the instructions in step 5.

5b. And you may also need TortoiseHg (http://tortoisehg.bitbucket.org/) if you haven't got it already either. Note that if an error such as "An error occured during the insallation of assembly 'Microsoft.VC90.CRT, version="9.0.30729.1",publikKeyToken-"1fc8b3b9a1e18e3b", processorArchitecture="amd64", type="win32". Please refer to Help and Support for more information. HRESULT:0x80070BC9." occurs, you have outstanding updates/reboots, so you need to reboot your Windows first before installing Tortoise hg 2.4.1 (see https://bitbucket.org/tortoisehg/thg-winbuild/issue/26/error-when-attempting-to-install-ver-241 for more information).

Note that using the hg clone command will create a libtcod directory inside C:\Libtcod. I've kept the structure of C:\Libtcod\libtcod\ though you may want to squish it to make it neater.

6. Now, before compiling libtcod, we have to copy over compatible 64-bit SDL library and headers. To do this, first create a new directory in C:\Libtcod\libtcod\dependencies called SDL-1.2.20. We're going to duplicate the layout of the existing SDL-1.2.15 directory, only with the updated 64-bit files.

Make a nested subdirectory called include\SDL (for example C:\Libtcod\libtcod\dependencies\SDL-1.2.20\include\SDL) and copy all the header files (*.h) from the equivalent directory in the 64-bit SDL distribution (in my case C:\SDL\SDL-1.2-20111107-win64\include\SDL) into it.

Note that the SDL-1.2.15 directory that comes with Libtcod contains two additional .h files not in the .20 verson - SDL_config_win32.h and SDL_copying.h. No idea if these are needed, but I copied these over also.

Now make a lib\mingw\ nested subdirectory (for example C:\Libtcod\libtcod\dependencies\SDL-1.2.20\lib\mingw) and copy over libSDL.dll.a from the lib subdirectory of the 64-bit SDL (e.g. C:\SDL\SDL-1.2-20111107-win64\lib) into ...\SDL-1.2.20\lib\mingw.

Verifiy that the contents of the SDL-1.2.15 and SDL-1.2.20 directories now mirror each other. Remember that the .20 version will contain 64-bit versions of SDL.

6a. You will also have to replace the SDL.dll file in the root libtcod directory (e.g. C:\Libtcod\libtcod\SDL.dll) with the one from the 64-bit SDL distribution (found in \bin there, e.g. C:\SDL\SDL-1.2-20111107-win64\bin\SDL.dll).

7. Now go to back to the libtcod directory and go into the makefiles directory (in my case it is C:\Libtcod\libtcod\makefiles). Make copies of the makefile-mingw and makefile-samples-mingw files and name them makefile-mingw-64 and makefile-samples-mingw-64 respectively.

8. Editing makefile-mingw-64, you need to change the following lines:

#9: From "SDL_DIR=dependencies/SDL-1.2.15" to "SDLDIR=dependencies/SDL-1.2.20"
#22: From "CC=mingw32-gcc" to "CC=x86_64-w64-mingw32-gcc"
#23: From "CPP=mingw32-g++" to "CPP=x86_64-w64-mingw32-g++"


This is to ensure that the proper 64-bit version of mingw that we installed in Step 1. is used to compile.

9. Editing makefile-samples-mingw-64, you need to change the following lines:

#6: From "SDLDIR=dependencies/SDL-1.2.15"
#8: From "CC=mingw32-gcc" to "CC=x86_64-w64-mingw32-gcc"
#9: From "CPP=mingw32-g++" to "CPP=x86_64-w64-mingw32-g++"


10. Now you can compile libtcod, and the samples. To do this, replace the makefiles targeted in the make commands given in "Compiling libtcod" with the new 64-bit makefiles:

"make -f makefiles/makefile-mingw" becomes "make -f makefiles/makefile-mingw-64"

and

"make -f makefiles/makefile-samples-mingw" becomes "make -f makefiles/makefile-samples-mingw-64"

Note that the call to the UPX executable packer may fail at the end of the build process. Ignore this, its not that important (IMHO).

11. You can now run the 64-bit samples app to make sure it runs ok. Which it should.  :)

12. For informational purposes, here are my relevant Code::Blocks "Build Options" for compiling a 64-bit libtcod project successfully in C++11. Please remember to highlight the top-level build option on the left-hand side of the "Project Build Options" to apply these to both Debug and Release and any other build types you have defined":

Compiler Settings/Compiler Flags:

Checked: "Have G++ follow the C++11 ISO C++ Language Standard [-std=c++11]"

Compiler Settings/Other Options:

-Wno-c++0x-compat
-isystem "C:/Libtcod/libtcod/include" (this is set to avoid generating warnings for Libtcod files when building)


Linker Settings/Link Libraries:

Add ..\..\..\Libtcod\libtcod\lib\libtcod-mingw.a (in my case this is C:\Libtcod\libtcod\lib\libtcod-mingw.a, i.e. it refers to the rebuilt libtcod library)

Search Directories/Compiler:

..\..\..\Libtcod\libtcod\include (in my case this is C:\Libtcod\libtcod\include)

Pre/post build steps/Post-build steps:

cmd /c copy "C:\Libtcod\libtcod\lib\libtcod-mingw.a" $(TARGET_OUTPUT_DIR)
cmd /c copy "C:\Libtcod\libtcod\libtcod-mingw.dll" $(TARGET_OUTPUT_DIR)
cmd /c copy "C:\Libtcod\libtcod\SDL.dll" $(TARGET_OUTPUT_DIR)


(note the quotes around the paths. Although not needed in this case, Code::blocks doesn't handle spaces in paths very well (see Step 1)).

And success!

2
(cross-posted from libtcod forums (http://doryen.eptalys.net/forum/index.php?topic=1628.msg9184#msg9184), I'm interested in a wider view)

So here are a few thoughts on how to provide animation in a libtcod roguelike project, building on top of the data and structures used in the C++ tutorial (http://codeumbra.eu/series/complete-roguelike-tutorial-using-c-and-libtcod).

Feel free to criticise and give feedback. Note this is probably incredibly *unoptimised*.

The basic idea is to use threads (sorry! :p). A worker thread would be used to handle animation of certain tiles during any time that nothing is happening.

I've split off any direct access to the TCODConsole::root into a singleton class called Console which is essentially just a wrapper around method calls to TCODConsole::root (and also allows me to work with std::strings in my code, and it handles the conversion to c_str() where possible). All calls to this need to be mutexed.
 
The update (i.e. AI/respond to player input)/render (draw on screen) methods in the Engine/GUI classes (which contain calls to change the screen) would be critical sectioned, and hence the animate thread would wait until such time as these were completed and flushed().

So, we will need a new class, let's call it Animate.

We can then add a Animate* pointer to the Tile struct and to the Actor class.

An animation object basically contains a vector of Frame structs, as well as a interval (int, in milliseconds, with a reasonable minimum granularity such as 0.1 seconds or so), screen x and y positions, and a Frame* NextFrame pointer. A Frame struct is a POD-object which consists merely of a char, a foreground TCODColor and a background TCODColor (note all these can be NULL/not set).

Essentially, this represents a situation where every interval, at the x any y position on screen, the glyph, background and foreground colours will change. (If the values are the same as previous, then no change in that value should occur).

So to animate a tile, one creates an appropriate Animate object and attaches it to the Tile (or Actor!), presumably at map creation.

So. The main thread maintains a vector of Animate* pointers. Every time the FOV is recalculated, this vector is populated (repopulated - again, room for optimisation here) with any Animate* belonging to tiles/actors currently in view. This needs to be critical sectioned/mutex off of course.

That's all the main thread does.

Meanwhile, the worker thread, every 0.1 seconds, or whatever we've decided the mininum granularity is, comes along, and starts at the top of this vector of Animate*. It checks if an animation is due (a simple mod on current time would work here?), and if so, updates the screen with the contents of the *NextFrame struct (and increases/wraps around the *NextFrame pointer to the "next" Frame*. One the entire list is processed it *flushes* the root console.

Note that the worker thread does NOT make any changes to *any* data other than the screen.

I'd probably implement this in Boost, to allow cross-platformness (is that a verb?).

So. Thoughts?

3
Early Dev / Coming Soon: DAEMON
« on: October 08, 2011, 09:49:08 PM »
I'm currently coding an occult-themed roguelike called Daemon using C# and the libtcod.NET library. Its inspired by the medieval grimoire Ars Goetia and by the Qliphoth (the reverse of the Qabbalistic Tree of Life).

I'm aiming to get a first version out (under the GPL) by the end of the month or so. It will be a complete game with save/load and a win condition, as I've largely used the framework I wrote for my 7DRL, Stygia.

Here's some screenies to whet your appetite:






Higher-resolution pictures available from http://kharne-rl.blogspot.com/2011/10/daemon.html

I'll also be releasing another Alpha version (primarily a bug-fix) of Kharne soon. Even though I can't stand coding in Delphi anymore :p

Best,
P.


4
Early Dev / Kharne Alpha 23 now available.
« on: July 06, 2011, 11:42:44 PM »
{see next post}

5
Early Dev / 7DRL Blog: Stygia
« on: March 06, 2011, 08:16:51 PM »
Hi, I've started a blog to record progress on my just started 7DRL: Stygia, developed using C#/Libtcod.NET

It can be found at:

http://stygiaroguelike.wordpress.com/

Best,
P.
(Author of the abandoned RL in Progress: Kharne)

6
Programming / C# Point Class code
« on: May 04, 2010, 07:02:22 PM »
C# doesn't (unlike Delphi) has a cartesian point class so I went ahead and wrote one. All code public domain blah blah blah. Should work on Mono as well:

/* Sample Point Class for C# for use in Roguelikes. By Dave Moore (starbog@NOSPAMgmail.com) */
/* All Code here is Public Domain - no copyright whatsoever, use it however you want */

using System;

namespace Map
{
    // List of cardinal directions useful in offset and coordinate calculations
    enum CardinalDirection { None = -1, N = 0, NE = 1, E = 2, SE = 3, S = 4, SW = 5, W = 6, NW = 7 };

    // Point Class to handle Map Functions
    class Point
    {
        // Default Constructor
        public Point()
        {
            // Set the internal values
            this.x = 0;
            this.y = 0;
        }

        // Standard Constructor
        public Point(int X, int Y)
        {
            // Set the internal values
            this.x = X;
            this.y = Y;
        }

        // Limit the values of the point to an additional supplied arbitrary
        // bounds - useful when working with calculated values
        public Point(int X, int Y, int Min, int Max)
        {
            // Set the internal values
            this.x = X;
            this.y = Y;

            // Limit the x value if necessary
            if (this.x > Max) { this.x = Max; }
            else if (this.x < Min) { this.x = Min; }

            // Limit the y value if necessary
            if (this.y > Max) { this.y = Max; }
            else if (this.y < Min) { this.y = Min; }
        }

        // Copy Constructor - we need to use this because unlike C++ we cannot
        // overload the assignment operator
        public Point(Point P2)
        {
            // If we have a valid reference
            if ((object)P2 != null)
            {
                // Set the internal values
                this.x = P2.X;
                this.y = P2.Y;
            }           
        }

        // Override the ToString method
        public override string ToString()
        {
            return string.Format("[{0},{1}]", this.x, this.y);
        }

        // Overload the equality operator
        public static bool operator ==(Point P1, Point P2)
        {
            // If we have a valid reference
            if ((object)P1 == null) { return false; }
            if ((object)P2 == null) { return false; }

            // Check for full equality on both values
            return (P1.x == P2.x && P1.y == P2.y);
        }

        // Overload the non-equality operator
        public static bool operator !=(Point P1, Point P2)
        {
            // If we have a valid reference
            if ((object)P1 == null) { return false; }
            if ((object)P2 == null) { return false; }

            // Check for inequality on either values
            return (P1.x != P2.x || P1.y != P2.y);
        }

        // Overload the equals operator
        public override bool Equals(System.Object P2)
        {
            // If we have a valid reference
            if ((object)P2 == null) { return false; }

            // Check we can cast the incoming object to a Point
            Point p = P2 as Point;
            if ((System.Object)p == null) { return false; }

            // Check for full equality on both values
            return (this.x == p.x && this.y == p.y);
        }

        // Provide a custom GetHashCode function (needed when the Equals operator is
        // overridden
        public override int GetHashCode()
        {
            // Use XOR
            return this.x ^ this.y;
        }

        // Provide an equivalent of an assignment operator
        public void Set(Point P2)
        {
            // If we have a valid reference
            if ((object)P2 != null)
            {
                // Set the internal values
                this.x = P2.X;
                this.y = P2.Y;
            }
        }

        // Provide another equivalent of an assignment operator
        public void Set(int X, int Y)
        { 
            // Set the internal values
            this.x = X;
            this.y = Y;
        }

        // Return the euclidean distance between two points
        public int DistanceTo(Point P2)
        {
            // If we have a valid reference
            if ((object)P2 == null) { return -1; }

            // Return the distance (as an int, rounded down)

            return (int)Math.Sqrt((this.x - P2.x) * (this.x - P2.x) +
                (this.y - P2.y) * (this.y - P2.y));
        }

        // get the difference between two points
        public void SetFromOffset(Point P1, Point P2)
        {
            // Set the default values
            this.X = 0;
            this.Y = 0;

            // If we have a valid reference
            if ((object)P1 == null) { return; }
            if ((object)P2 == null) { return; }

            // Get the offsets
            this.X = (P1.x - P2.x);
            this.Y = (P1.y - P2.y);
        }

        // Return the difference between two points optionally limiting the
        // values returned
        public void SetFromOffset(Point P1, Point P2, int Min, int Max)
        {
            // Set the default values
            this.X = 0;
            this.Y = 0;

            // If we have a valid reference
            if ((object)P1 == null) { return; }
            if ((object)P2 == null) { return; }

            // Get the offsets
            this.X = (P1.x - P2.x);
            this.Y = (P1.y - P2.y);
     
            // Limit the x value if necessary
            if (this.X > Max) { this.X = Max; }
            else if (this.X < Min) { this.X = Min; }

            // Limit the y value if necessary
            if (this.Y > Max) { this.Y = Max; }
            else if (this.Y < Min) { this.Y = Min; }
        }

        // Get the direction of one point from another as an enum
        public CardinalDirection DirectionTo(Point P2)
        {
            // If we have a valid reference
            if ((object)P2 == null) { return CardinalDirection.None; }

            // Set up an offset array to convert the offsets of the two points
            // into a direction
            Point[] Directions = new Point[8];
            Directions[(int)CardinalDirection.N] = new Point(0, -1);
            Directions[(int)CardinalDirection.NE] = new Point(1, -1);
            Directions[(int)CardinalDirection.E] = new Point(1, 0);
            Directions[(int)CardinalDirection.SE] = new Point(1, 1);
            Directions[(int)CardinalDirection.S] = new Point(0, 1);
            Directions[(int)CardinalDirection.SW] = new Point(-1, 1);
            Directions[(int)CardinalDirection.W] = new Point(-1, 0);
            Directions[(int)CardinalDirection.NW] = new Point(-1, -1);
           
            // Get the offset from one point to another
            Point P = new Point();
            P.SetFromOffset(this, P2, -1, 1);

            // Find the matching direction
            int Index = 0;
            foreach (Point Item in Directions)
            {
                if (Item == P) { return (CardinalDirection)Index; }
                else { Index++; }
            }

            // Return the null value just in case
            return CardinalDirection.None;
        }

        // Check if two points are adjacent to each other
        public bool Adjacent(Point P2)
        {
            // Test if the points are 1 square apart
            return (this.DistanceTo(P2) == 1);
        }

        // Private data members
        private int x;
        private int y;

        // Publically accessible properties
        public int X
        {
            get { return this.x; }
            set { this.x = value; }
        }       
        public int Y
        {
            get { return this.y; }
            set { this.y = value; }
        }


    }

}


And here's a console test app as well:


/* Sample Point Class for C# for use in Roguelikes. By Dave Moore (starbog@NOSPAMgmail.com) */
/* All Code here is Public Domain - no copyright whatsoever, use it however you want */

using System;
using System.Text;
using Map;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Point P1 = new Point(10, 11);
            Point P2 = new Point(24, 20);
            Point P3 = new Point(100, 5);

            System.Console.WriteLine("C# Point Class Demo\n*******************\n");
            System.Console.WriteLine("Defined P1 as (10, 11), P2 as (24, 20) and P3 as (100, 5)\n");
            System.Console.WriteLine("Standard Constructor & ToString(): P1 = " + P1.ToString() + ", P2 = " + P2.ToString() + ", P3 = " + P3.ToString());
            Point P5 = new Point(999, -999, -9, 9);
            System.Console.WriteLine("Limit Constructor: P5 = (999, -999, -9, 9): P5 = " + P5.ToString());
            Point P4 = new Point(P1);
            System.Console.WriteLine("Copy Constructor (P4->P1): P4 = " + P4.ToString() + "\n");
            System.Console.WriteLine("==: P1 == P2 = " + (P1 == P2).ToString());
            System.Console.WriteLine("!=: P1 != P2 = " + (P1 != P2).ToString());
            System.Console.WriteLine("Equals(): P1.Equals(P2) = " + (P1.Equals(P2)).ToString());
            Point P6 = new Point(66, 66);
            P6.Set(P3);
            System.Console.WriteLine("(Assignment Operator) Set(): P6 = (66, 66), P6.Set(P3); P6 = " + P6.ToString());
            Point P7 = new Point(77, 77);
            P7.Set(7, 7);
            System.Console.WriteLine("(Direct Set Operator) Set(): P7 = (77, 77), P7.Set(7, 7); P7 = " + P7.ToString() + "\n");
            System.Console.WriteLine("DistanceTo(): P1.Distance(P2) = " + (P1.DistanceTo(P2)).ToString());
            System.Console.WriteLine("Adjacent(): P1.Adjacent(P2) = " + (P1.Adjacent(P2)).ToString());

            Point P8 = new Point(88, 88);
            P8.SetFromOffset(P1, P2);
            System.Console.WriteLine("SetFromOffSet(): P8 = (88, 88), P8.SetFromOffset(P1, P2) = " + P8.ToString());
            Point P9 = new Point(99, 99);
            P9.SetFromOffset(P2, P1);
            System.Console.WriteLine("SetFromOffSet(): P9 = (99, 99), P9.SetFromOffset(P2, P1) = " + P9.ToString());
            Point P10 = new Point(10, 10);
            P10.SetFromOffset(P1, P2, -1, 1);
            System.Console.WriteLine("SetFromOffSet(): P10 = (10, 10), P10.SetFromOffset(P1, P2, -1, 1) = " + P10.ToString() + "\n");
            System.Console.WriteLine("DirectionTo(): P2.DirectionTo(P1) = " + P2.DirectionTo(P1).ToString());
            System.Console.WriteLine("DirectionTo(): P1.DirectionTo(P2) = " + P1.DirectionTo(P2).ToString());

            Console.ReadKey(true);
        }
    }
}



Enjoy!

Best,
P.

7
Programming / Handling Transient Events and Effects: an example
« on: August 06, 2009, 12:38:51 PM »
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.



8
Programming / Things monsters would say!
« on: July 21, 2009, 07:15:54 PM »
As the title says, "Things monsters would say". Let's get our creative juices flowing and let's make our dungeons more atmospheric and our monsters more realistic!

I'll start with a few:

"I want to stab you in the head"
"You'll make a tasty stew"
"mine! all mine!"
"I won't let you steal my treasure!"
"I'll stick you full of arrows like a porcupine"
"shinies!"

9
Programming / Kharne Alpha 1 Available
« on: March 09, 2009, 01:25:00 PM »
(Edit: see below)

Shamed into action by the 7DRL folks, I've decided to release an alpha
version of the rewrite of Kharne this coming weekend or so.

Many features will be missing, and gameplay is non-existent yet (its
more of an interactive demo than anything else really), but it will give you an idea of what to expect when the game is finally finished (in the year 2112, probably).

Random screenshot:



It will be downloadable from the Kharne Devblog, http://kharne-rl.blogspot.com/ soon!

Feedback and criticism will be sought and most welcomed.

Best,
Dave


Pages: [1]