Lemma Soft Forums

Supporting creators of visual novels and story-based games since 2003.


Visit our new games list, blog aggregator, IRC, and wiki.
Activation problem? Email pytom@bishoujo.us.
It is currently Tue Jul 29, 2014 6:53 pm

All times are UTC - 5 hours [ DST ]




Post new topic Reply to topic  [ 248 posts ]  Go to page 1, 2, 3, 4, 5 ... 17  Next
Author Message
PostPosted: Sat Sep 01, 2012 8:53 pm 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
Image Image Image
Image Image Image

(Previous thread describing alphas 1-6 can be found here: viewtopic.php?f=16&t=7207 )

This engine allows you to insert RPG-style battles into your Ren'Py games, supporting FF-style 'active' battles, arbitrary-path-based battles, grid-based, isometric, hex-based and now elevation isometric-tile-based battles.

The system supports many typical RPG features, including a modular skill system, pre-fab skills for melee combat and magical attacks, items and equipment, experience and levelling-up, conditional events, animation and more. All these features are written to be highly customisable to make it easier to tailor the engine to your particular game rules and conventions.

To keep up to date with battle engine development, you may want to subscribe to the newsletter.

A7.5 Files

The latest release can be downloaded here:
battleengine-a7.5.zip (11.4MB Zip)

(The zip is a zipped-up game directory; unzip it to whichever directory you keep your Ren'Py projects in.)

Commercial Licensing

There's now a page on licensing on the battle engine website; you can find it here. If you're releasing your game for free, there's nothing to worry about, it's a standard CC-BY-NC license; if you're interested in talking about a commercial license for your game, then go and read the article and then get in contact:

http://www.eviscerate.net/article/licensing



Previous Releases
Alpha 7 [11.3MB]
Alpha 6 [9.9MB]
Alpha 5 [25MB]
Alpha 4 [25MB]
Alpha 3 [15MB]
Alpha 2 [21MB]
Alpha 1 Windows [11MB]
Alpha 1Mac OSX [15MB]
Alpha 1 Leinnucks [11MB]

License

Image
The battle engine is released under the Creative Commons Attribution Non-Commercial 2.0 UK license. This means that you may use it in and adapt it for your games, but you must a) give due credit, and b) not use it in a commercial project.

I'm perfectly happy to discuss commercial licensing terms if people want to; contact me through PM or via the contact form on my website.

New Features in A7.5

In the latest release - Alpha 7.5 - the following features have been added:
  • Tilemap rotation - view the battlefield from different angles
  • New TileUIProvider to make it easier to customise the square highlight graphic
  • Tilemaps and elevation now work with hex grids
  • Grid and HexGrid Battlefields now use matrix-based projection
  • Supporting the rotation, tiles in a tilemap can have alternate graphics to be used at different rotations, like sprite facing.
  • Various animation fixes, including repeating magic-attack anims and a fixed current-fighter cursor while walking
  • Fighter stats stored as floats, and just rounded upon display
  • Experience bonuses fixed to avoid problems with stat-boosting equipment/effects
  • Haste skill example modified to have a timeout period
  • Sub-skill trees now sort correctly according to weight
  • Special narrator character no longer used for battle announcements, to avoid breaking people's games!

Notes for A7.5
  • UIProvider has been moved out of engine-display.rpy and into its own file in engine-ui.rpy
  • Reset method added to BattlePanner
  • The 'offset' parameter has been removed from GridBattlefield and HexGridBattlefield - it was nearly always set one of two ways, and it's been replaced by an 'isometric' parameter. Set isometric=True to have the isometric grid, and False for a straight perpendicular-to-the-screen rectangular grid. This was done during the changes to use matrix projection for the grids - if you want something different, you'll need to subclass GridBattlefield and write your own projection matrix.
  • The 'offset' parameter has also been removed from the RPGDamage Extra - it wasn't actually being used, it pre-dated the placeMark setting on sprites, and should have been removed several versions ago. If you're still passing it in, though, it'll now break.
  • Subskill trees now sort correctly by weight. Which isn't so much of a 'breaking change' as a 'bugfix', but if you're relying on alphabetical sorting in your subskill trees, you'll need to add weights to the commands now or they won't show in the same order any more.

Feature List

  • 'Active' battle mechanic
  • simple turn-based battle mechanic
  • card-counter wargame battle mechanic
  • FF-style simple face-to-face battlefields
  • Abraxas-demo-style fixed-path battlefields
  • Tactics-style Grid-based battlefields
  • Tilemap-based battlefields with variable height and rotation
  • Hex-grid battlefields
  • Basic enemy AI
  • State-and-facing-aware sprites (so you can have death animations or attack animations or walk in particular directions, etc.)
  • Plug-in skills (so far I've done obvious stuff like 'attack', 'defend', 'skip turn', 'move' and limited magic)
  • Scenery (single and multi-square)
  • Attribute system (e.g. an enemy can have the 'fire' attribute, which makes them weaker to attacks using the 'water' attribute)
  • Highly customisable (pick-and-mix to a degree for non-programmers; very open to customisation and extension for programmers)
  • Items, Inventory and usage
  • Equipment and equip limits
  • Panning
  • Experience and levelling
  • Condition/result event/mission parameter system
  • Equipping, Party-select and Shop screens to facilitate RPG creation

Screenshots

Image Image Image

Image Image Image

Image Image Image

_________________
Image


Last edited by Jake on Sat Jan 11, 2014 2:48 pm, edited 5 times in total.

Top
 Profile Send private message  
 
PostPosted: Sat Sep 01, 2012 8:53 pm 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
Here's an example of setting up a Final-Fantasy-style 'active' battle:

Code:
    python:
       
        battle = Battle(ActiveSchema())

        fieldSprite = BattlefieldSprite('bg woodland')
        battlefield = SimpleBattlefield(fieldSprite)
        battle.SetBattlefield(battlefield)
       
        battle.AddFaction("Player", playerFaction=True)
       
        bobSprite = BattleSprite('bob', anchor=(0.5, 0.75))
        bob = PlayerFighter("Bob", Speed=11, Attack=20, Defence=20, sprite=bobSprite)
        bob.RegisterSkill(Library.Skills.SwordAttack)
        bob.RegisterSkill(Library.Skills.Skip)
        battle.AddFighter(bob)
       
        geoffSprite = BattleSprite('geoff', anchor=(0.5, 0.8))
        geoff = PlayerFighter("Geoff", Speed=13, Attack=7, Defence=10, MP=20, sprite=geoffSprite)
        geoff.RegisterSkill(Library.Skills.SwordAttack)
        geoff.RegisterSkill(Library.Skills.Skip)
        geoff.RegisterSkill(Library.Skills.Fire1)
        geoff.RegisterSkill(Library.Skills.Water1)
        geoff.RegisterSkill(Library.Skills.Earth1)
        battle.AddFighter(geoff)
       
        battle.AddFaction('Enemies', playerFaction=False)
       
        banditSprite = BattleSprite('bandit', anchor=(0.5, 0.75), placeMark=(0,-75))
       
        bandit1 = SimpleAIFighter("Bandit 1", Speed=10, Attack=15, Defence=8, sprite=banditSprite)
        bandit1.RegisterSkill(Library.Skills.KnifeAttack, 1)
        battle.AddFighter(bandit1)

        bandit2 = SimpleAIFighter("Bandit 2", Speed=10, Attack=15, Defence=8, sprite=banditSprite)
        bandit2.RegisterSkill(Library.Skills.KnifeAttack, 1)
        battle.AddFighter(bandit2)

        bandit3 = SimpleAIFighter("Bandit 3", Speed=10, Attack=15, Defence=8, sprite=banditSprite)
        bandit3.RegisterSkill(Library.Skills.KnifeAttack, 1)
        battle.AddFighter(bandit3)
       
       
        battle.AddExtra(RPGDamage(offset=(0, -75)))
        battle.AddExtra(RPGDeath())
        battle.AddExtra(ActiveDisplay("Player", {"HP": "Health", "Move": "Move", "MP":"MP"}))
        battle.AddExtra(RPGActionBob())
        battle.AddExtra(SimpleWinCondition())
       
        battle.Start()
       
        winner = battle.Won
   
    if (winner == 'Player'):
        "Well done, you beat the bad guys."
    else:
        "Game Over: You Died."


(This is actually one of the demo scripts, just with the copious comments removed.)

_________________
Image


Top
 Profile Send private message  
 
PostPosted: Sun Sep 02, 2012 4:30 am 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
You are awesome. This is one of the best engines I've seen and not just in Ren'py. I'm really looking forward to the height system. You even included a way to get the visible portion off the bat!

Does the height system work with hexes by default?


Top
 Profile Send private message  
 
PostPosted: Sun Sep 02, 2012 4:37 am 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
CaseyLoufek wrote:
Does the height system work with hexes by default?


Yes and no? The hex battlefield is based heavily on the grid battlefield, so it will inherit all the intrinsic support for height in the LoS algorithm and the checks in move/pathfinding and so on... but I've not included a hex version of the tilemap battlefield which is easy to set up and draws everything at the right height. I'll be working on it, but since I've not seen anyone using the hex battlefield at all, I figured it could wait 'til the next release...(!)

_________________
Image


Top
 Profile Send private message  
 
PostPosted: Sun Sep 02, 2012 9:14 pm 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
I needed to update GetRange in BattleHexGridContoller to get my AIs movement working again. Gonna take a look at setting up a hex grid tilemap myself, I'll let you know how it goes.

Code:
        def GetRange(self, pos1, pos2, callback=None, useHeight=True):
           
            #TODO: use callback to count the actual cost of intervening squares?
           
            dX = max(pos1.X, pos2.X) - min(pos1.X, pos2.X)
            dY = max(pos1.Y, pos2.Y) - min(pos1.Y, pos2.Y)
           
            dist = max(dX, dY)
               
            if useHeight:
                dist = dist + math.fabs(pos1.Height - pos2.Height)
               
            return dist


Top
 Profile Send private message  
 
PostPosted: Sun Sep 02, 2012 11:03 pm 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
Some very basic files from my proof of concept test. It now works with hexes more or less bug free though I've barely tested it. Fighter Z order may need tweaking.

EDIT: Removed files as it need some work but I've got the basics functioning.


Top
 Profile Send private message  
 
PostPosted: Mon Sep 03, 2012 7:32 am 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
Casey: for what it's worth, I'm part-way through working on tileset and elevation support for hex battlefields myself, if you want to wait for that.

I'll be releasing an Alpha 7.1 soon anyway, as I finally found and fixed a long-standing animation problem last night with the help of PyTom - it turns out that hiding and re-showing a displayable using renpy.show doesn't re-set the animation timebase if you're using an ImageReference around a named image, but passing in a named image directly works fine...

_________________
Image


Top
 Profile Send private message  
 
PostPosted: Mon Sep 03, 2012 2:08 pm 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
Actually my last error was because I accidently swapped in an Iso hex (for the X rows offset) tile which of course broke the graphics.

Not perfect but it's more or less working.


Attachments:
hex-rock.png
hex-rock.png [ 12.35 KiB | Viewed 9247 times ]
elevation.rpy [20.36 KiB]
Downloaded 54 times
engine-battlefields.rpy [67.66 KiB]
Downloaded 68 times
Top
 Profile Send private message  
 
PostPosted: Tue Oct 09, 2012 11:07 pm 
Newbie
User avatar

Joined: Sat Sep 15, 2012 11:17 am
Posts: 5
Location: Omaha, NE
Projects: Eight Challengers
Hiya, I've been playing with the engine on-and-off for the past week or so, and I'm really happy about how flexible it is. In fact, I tried loading up 20 animated Clydes just to see if the engine could take it, lo and behold: no performance hit. ('Course, ::effort:: to load up 40ish, which is probably the worst I plan on ever trying...)

Question about trying to save in mid-battle: It's just not going to happen, is it? You get a half-rollback that saves stats but resets to the initial state of the battle, or a full reset if you quit and reload. I suspect this is more because of Renpy's saving limitations than anything else, so unless you have any thoughts on this, I think I'm going to have to just disable saving to prevent weird stuff from happening. (Enforced difficulty? Dohohoho.)


Top
 Profile Send private message  
 
PostPosted: Wed Oct 10, 2012 2:31 pm 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
Dite wrote:
Question about trying to save in mid-battle: It's just not going to happen, is it?
...
I suspect this is more because of Renpy's saving limitations than anything else


Yes and no... it's due to Ren'Py's saving limitations that you get the behaviour that you see, but I suspect that it's probably possible to work around it. I just haven't tried, because I just figured that it was preferable to not be able to save in battles!

Rollback in battles would be a bit more difficult, although I suspect that's also possible if enough work were put into it.



On an unrelated note, here's what I've been working on lately - rotation!

Attachment:
File comment: Isometric square grid rotation
rotate-iso.png
rotate-iso.png [ 436.61 KiB | Viewed 8691 times ]


Attachment:
File comment: Hex grid rotation
rotate-hex.png
rotate-hex.png [ 290.6 KiB | Viewed 8691 times ]


It unfortunately doesn't work as well as I'd have liked thanks to a limitation of Ren'Py; I'd have preferred that, like the panning controls, the user could rotate at any time. Unfortunately that would require changes to z-order in a transform function, Ren'Py doesn't allow z-order to be governed by a transform, and PyTom has said that it's a fundamental enough thing that it's likely never going to change. So the user can only rotate when an appropriate UI function is called - presently it's implemented in PickTargetPosition, PickTargetFighter and PickFighter, IIRC.

To do this, the GridBattlefield and HexGridBattlefield position-munging has been replaced by matrix transforms, which does mean that it's no longer possible to specify a skewed grid as part of the GridBattlefield construction... but hopefully it's easier to understand what numbers to put where as well, and I don't see a skewed grid being all that useful, so hopefully it's OK...! The advantage is that other view transformations will be easier to implement in the future - I'm thinking of zoom in/out in particular.

I'll release this as soon as I have the extra animations done to have Clyde wandering around a hex map with elevation - which may be as soon as the weekend, depending on finding the time! Since it's mostly fixes and there's no big functional changes, it'll probably be Alpha 7.5 or something...

_________________
Image


Top
 Profile Send private message  
 
PostPosted: Wed Oct 10, 2012 5:59 pm 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
Well that defeats the purpose of my IsoHexGrid and HexGrid classes. Not that I'm complaining mind you. :)

Saving in battles? I could make that happen, it might require some special set up for each game though. Rollback? Possible but ugly and not worthwhile I think. Saving in combat can be abused but rollback would be absurd.

Also, is the jumping and heights going to get any more mechanical improvement? As is the system is mostly graphical and not well set up to handle adjusting movement costs and such. Ideally I want options like range getting the Z difference and the X/Y difference and taking the hypotenuse. I can do this myself of course but after seeing your flipping Hex Grid and how busy I currently am I figure I'd ask.


Top
 Profile Send private message  
 
PostPosted: Wed Oct 10, 2012 7:06 pm 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
I did tell you I was working on them! ;-) I'd have got this stuff out earlier if I'd just given up on trying to work around the Ren'Py x-order limitation sooner...

CaseyLoufek wrote:
Also, is the jumping and heights going to get any more mechanical improvement? As is the system is mostly graphical and not well set up to handle adjusting movement costs and such. Ideally I want options like range getting the Z difference and the X/Y difference and taking the hypotenuse.


Looking at my SVN logs, the jumping-over-gaps part has had a couple of changes since the A7 release:

- Rather than just amending X/Y a second time in the same direction as the first, it now considers as candidates for a jump any position which is a) joined to from the intermediate position and b) not joined to from the start position. This was necessary to get it properly working with hexes, of course, as there are 12 valid candidates from a hex start point, but only 6 directions to move in.
- Jump costs are now based on battlefield range with an extra arbitrary 0.5 penalty rather than just fixed at 2. Firstly this makes them priced more reasonably, secondly the arbitrary penalty makes the jump generally less preferable than just walking when both options are available (such as jumping across diagonals on a square grid, which to my mind should be possible when the two intermediate squares are non-traversable, but looks funny if either of them are).

The idea with the MoveSkill class is that if you want to change the costs associated with moving up or down (or anywhere, for that matter), then you subclass the MoveSkill class and re-implement the GetCallback method to return a new callback function which calculates your costs. The default one will allow movement between +0.5 and -2 height from the current square, and charge it at 1 + (0.5 * difference in height) after allowing a free 0.5 downwards height change before charging extra for it (to make it easier to go downhill than uphill).
The same callback also deals with the way the jumping-across-gaps works - it takes a source square as parameter, and is expected to return a dict mapping all valid squares to move to from the source square to their associated costs from that source square. Another planned feature before I call all this 'more or less finished' is properties or attributes on positions, so certain positions could be marked as 'bog' and cost more movement points to enter, or 'road' and cost fewer, for example; all this would be handled in the callback as well. It's intended to end up as a game-logic method more than an engine method, in the same way that it's intended that people should write their own UIProvider class if they want to change the way that the UI interactions are handled.

I'm not against the idea of adding extra parameters to the MoveSkill initialiser to control some of that - maybe named params for how far up or down a fighter can move and how much up or down movement they get for free - but I don't want to go into too much detail 'cause it's still basically making the assumption that the game-maker even wants to use that model of movement costs. If you can see any way in which the degree of control is still too lacking, let me know and I'll see if I can address it.



The range method (which is used as a base for jump costs) should in both square and hex grids be the 3-dimensional distance between the centre of the ground of the start position and the centre of the ground of the end position. LoS should also function similarly - only in the case of LoS, it will replace "the centre of the ground" with "a point <fighter-height> above the centre of the ground" for any position - source or target - which has a fighter in it.

_________________
Image


Top
 Profile Send private message  
 
PostPosted: Wed Oct 10, 2012 7:39 pm 
Newbie
User avatar

Joined: Sat Sep 15, 2012 11:17 am
Posts: 5
Location: Omaha, NE
Projects: Eight Challengers
Oh, no, I think rollback in battle would be a nightmare from a balance perspective. Like, the first thing I did when I cracked the demo open was hammer the rollback button to make sure it wasn't enabled as an oversight or something. Something about the idea horrifies me.

I was just pondering in-battle saving since it's one of those convenience things... Suppose you wanna put the game down and go to bed or something. (I'm pretty laissez-faire about reloading, since the player can use it less if they think it's easymode. Blahblah, opinions about game design.) That said, I flunked most of my flow-related assignments in school and I'm far from understanding enough of the meat about saving to tackle that myself. It's no prob, not losing sleep over it. Thanks for answering!

Other thing: I think I'm just being dumb or something, but I'm using what would be legit-ish syntax for a skill on an item, and it's not recognizing the attribute. No crash, just non-reaction. (I've got it so that I'd get some text if 'stab' is detected, and that piece is working fine on a test skill.)
Code:
Library.Equipment.POINTY = Weapon("POINTY", attributes=['stab'], attack=5, cost=5)

I'm under the impression from engine-items and example-items that this is doable; could you provide a sample of how a weapon with attributes is supposed to look?


Top
 Profile Send private message  
 
PostPosted: Thu Oct 11, 2012 2:01 am 
Regular

Joined: Sat May 28, 2011 1:15 am
Posts: 142
Projects: Bliss Stage, Orbital Knights
Honestly I'm glad I did that Hex class work. My understanding of the inner workings is a lot more complete now and I can write better subclasses.

Quote:
- Jump costs are now based on battlefield range with an extra arbitrary 0.5 penalty rather than just fixed at 2. Firstly this makes them priced more reasonably, secondly the arbitrary penalty makes the jump generally less preferable than just walking when both options are available (such as jumping across diagonals on a square grid, which to my mind should be possible when the two intermediate squares are non-traversable, but looks funny if either of them are).


This fixes a major part of my concern and I think the other part covers a bug I was seeing on the hex maps.

I'm fine with MoveSkill working as described, just wanting to know if I'd need to be subclassing from another Move class or if it would be in by default or what. I've figured out the callbacks from doing the Hex work and mostly wanted to know how they've been updated as I plan out my game.

One issue I've seen is movement costs differing oddly. I'm chalking this up to the hex jump bug and also the rounding of floats. The height costs are given in floats but they get rounded off when using the default movement ints. So a player can move 2.35 and then 1.15 with 3 points of movement. I'm getting floats in my callback and trying to subtract but I can't seem to get movement to act as a float. I suppose worse comes to worse I can just multiply things by 10 or 100 overall.


Top
 Profile Send private message  
 
PostPosted: Thu Oct 11, 2012 3:14 am 
Support Hero
User avatar

Joined: Sat Jun 17, 2006 7:28 pm
Posts: 3824
Dite wrote:
Oh, no, I think rollback in battle would be a nightmare from a balance perspective


You say that, but the recent-ish PSP re-release of Tactics Ogre: Let Us Cling Together - a classic of the tactical-RPG genre - has added just such a feature! IIRC it lets you roll back something like 30 turns, which is going to be a significant chunk of if not the entire battle. It certainly does make things easier if you want to resort to it, since you can back out of bad decisions or bad luck, but I wouldn't say it's a nightmare. Of course, strictly speaking it's no worse than allowing saving and reloading during battles, since a player could theoretically do that every single turn with every single fighter and revert back to whichever save he wanted...! ;-)

(One big motivator to blocking rollback in battles for me was that I kept doing it by accident during testing, which was rather frustrating since without any extra attention it does the same thing a save and reload would - which generally dumps you back to the beginning of the battle!)

Dite wrote:
Other thing: I think I'm just being dumb or something, but I'm using what would be legit-ish syntax for a skill on an item, and it's not recognizing the attribute. No crash, just non-reaction. (I've got it so that I'd get some text if 'stab' is detected, and that piece is working fine on a test skill.)
Code:
Library.Equipment.POINTY = Weapon("POINTY", attributes=['stab'], attack=5, cost=5)

I'm under the impression from engine-items and example-items that this is doable; could you provide a sample of how a weapon with attributes is supposed to look?


The code looks fine - where and how are you checking for the 'stab' attribute? Attributes on equipment are currently only really used in the FighterEquipment classes - for example, the 'hand' attribute will be counted by the HandedFighterEquipment class. They don't come through to attacks like the attributes on attack skills do, because there's presently no link between the equipment and the attack - if all equipment attributes were transmitted to attacks, then your armour would also contribute attributes!

A piece of work I have on my to-do list - which I'll probably get to as part of the next main release, after the minor release I have planned for Very Soon - is to allow equipment items to impart skills. This would mean that, for example, you don't necessarily have to add an Attack skill to your fighter - adding a weapon equip would grant the Attack skill automatically. When this is done, the attributes from the weapon will be passed through that skill, so you can do stuff like create a fire-elemental sword which makes attacks which have the 'fire' attribute.


CaseyLoufek wrote:
One issue I've seen is movement costs differing oddly. I'm chalking this up to the hex jump bug and also the rounding of floats.


Certainly there would have been weirdness if you hadn't modified/reimplemented the existing callback at all to work with a hex grid - because hexes aren't continuous in the same axes as the coordinate system, the approach taken in the A7 callback would have - IIRC - covered four of the 12 potential jump candidates and two hexes that should never have been considered! That much is definitely fixed, and I have made a note in my pre-release to-do list to check that move costs aren't being rounded off anywhere. I'm pretty sure I've had it working without rounding - so, say, a fighter can move 4 squares along the flat, for example, but only 3 if there's an upward movement part-way 'cause that upward movement costs 1.25 or something.

_________________
Image


Top
 Profile Send private message  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 248 posts ]  Go to page 1, 2, 3, 4, 5 ... 17  Next

All times are UTC - 5 hours [ DST ]


Who is online

Users browsing this forum: No registered users


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Protected by Anti-Spam ACP
Powered by phpBB® Forum Software © phpBB Group