Sign in to follow this  

C++ factory problem

This topic is 2847 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've got an abstract base class BasicGame which has 2 subclasses AIGame and HumanGame, the type of game used is dependant on commandline arguments at runtime so the main executable has a BasicGame declared as a variable. Obviously I can't instantiate an abstract class so I've got a pointer to the base class. My question is this, how the heck do I convert from the abstract base class pointer into an instance of the subtype (presumably will also have to be a pointer)? I know I could declare an instance of both types, instantiate only 1 of them and only call the methods on the instantiated one but that's clearly a horrible hack.
BasicGame *game;

void createGame(HWND hWnd, HINSTANCE hInstance, LPSTR flags)
{
	RECT screen;
	GetClientRect(hWnd, &screen);
	if(strcmp(flags, "-ai") != 0)
	{
		*game = HumanGame(hInstance, hWnd);
	}
	else
	{
		*game = AIGame(hInstance, hWnd);
	}
}

when I run that I get an error in __vfptr and in all members of the subtype class: CXX0030: Error: expression cannot be evaluated. Cheers, Sepharion

Share this post


Link to post
Share on other sites
The cause of the issues is that you are not actually allocating the game object in question. Once you do that you can just use the interface to invoke whichever actions need to be done.

For example:
<code>
game = new HumanGame(hInstance, hWnd);
</code>

Share this post


Link to post
Share on other sites
oluf - the game is generally set up the same regardless of which type of game so I'm using the base class to do that, later when I read the commandline input it should determine whether it is a HumanGame or an AIGame to add the specific implementation for a couple of methods.

dysfictional - Can it not be done keeping the game on the stack or do I actually need to give it heap space?

I like oluf's idea of casting the BasicGame pointer to a HumanGame or AIGame pointer but I've never really used static casts before and I'm pretty unsure how I would do it on an abstract class.

The example from cplusplus.com

class CBase {};
class CDerived: public CBase {};
CBase * a = new CBase;
CDerived * b = static_cast<CDerived*>(a);




In this case CBase would be BasicGame however because it is abstract it cannot be instantiated.

Share this post


Link to post
Share on other sites
I think you're confused (and the 'cplusplus.com' example code is also confused). Your original code:

BasicGame *game;
*game = HumanGame(hInstance, hWnd);
Takes whatever HumanGame() spits out and does a by-value copy of it into storage that hasn't been allocated yet; most likely slicing it in the process.
To do what you want you can either allocate dynamically (using new), or take pointers to existing (statically allocated) objects. Either of the following functions will give you the pointer you want:-

BasicGame *HumanGame()
{
static HumanGameObject obj;
return &obj;
}

BasicGame *HumanGame()
{
return new HumanGameObject(); // preferred
}

BasicGame *game = HumanGame();



Jans.

Share this post


Link to post
Share on other sites
Quote:
Original post by sepharion
...
I like oluf's idea of casting the BasicGame pointer to a HumanGame or AIGame pointer but I've never really used static casts before and I'm pretty unsure how I would do it on an abstract class.
...


And why would you want to cast BasicGame pointer to HumanGame or AIGame? Can you show some code about it, since it may be unnecessary.

Regards!


Share this post


Link to post
Share on other sites
use game = new HumanGame(hInstance, hWnd); not *game = HumanGame(hInstance, hWnd);

The example you have shown (from cplusplus.com):

The Base class is not abstract. Classes are abstract there is AT LEAST one pure virtual function inside. And if you want to create a derived class instance do it like that:


Base * child = new Derived;



There is no need of static_cast here. AFAIK static casts are for objects whose types are COMPLETELY different from each other like:

class Base1{};
class Base2{};

Base1 * base1 = new Base1;
Base2 * base2 = static_cast<Base2 *>(base1);


So when you deal with Derived-Base relation use implicit casting, not explicit castings (dynamic_cast, static_cast, reinterpret_cast)

Share this post


Link to post
Share on other sites
kauna - because updateWorld in HumanGame differs drastically from the functionality in AIGame so is a pure virtual function in BasicGame.

kasya - I meant in my case the base class is abstract because updateWorld() is a pure virtual function. Having read up on casts a dynamic cast will cast from subtype to base type only, static cast can do it either way, reinterpret casts can change it from any 1 thing to any other but is likely to cause crashes at runtime.

jans - Roughly what i've got now which seems to work but has broken other things.


void createInstances(HWND hWnd, HINSTANCE hInstance, LPSTR flags)
{

RECT screen;
GetClientRect(hWnd, &screen);
if(strcmp(flags, "-ai") != 0)
{
game = new Game(hInstance, hWnd);
}
else
{
game = new AIGame(hInstance, hWnd);
}

}



seems to be doing the trick, although something that I've noticed now is that my animation has broken (and I think it has something to do with this). Every time the control loop inside my updateWorld() method is called it skips a method call. For simplicities sake if the player holds the right arrow key walkRight() is called inside the base class. walkRight() does checks to make sure the player isn't against a wall and should handle which frame of animation is displayed, however something is preventing this method from being called.



void BasicGame::walkRight()
{
if(!player.goingRight())
{
player.setGoingRight(true);
player.setFrameNumber(0);
startDirTime = timeGetTime(); // startDirTime defined as a DWORD
}
else
{
DWORD timeInDir = timeGetTime() - startDirTime;
if(timeInDir > 100)
{
nextWalkFrame(); //debugger gets here but never enters method

startDirTime = timeGetTime();
}
}

if(!collision('l'))
{
player.walkLeft();

}
}

void BasicGame::nextWalkFrame()
{
player.setFrameNumber(player.getFrameNumber() +1);

if(player.getFrameNumber() > 8) // 8 frames of walking animation
{

player.setFrameNumber(0);
}

}




Any idea why this is being optimised out or is there a way to turn optimisation off?

Cheers,
Sepharion

Share this post


Link to post
Share on other sites
Quote:
Original post by sepharion
because updateWorld in HumanGame differs drastically from the functionality in AIGame so is a pure virtual function in BasicGame.




class BasicGame
{
virtual void UpdateWorld() = 0;
}

void AIGame::UpdateWorld()
{
//implement the AIGame version of update world
}

void HumanGame::UpdateWorld()
{
//implement the HumanGame version of update world
}

//inside your game loop

game->UpdateWorld(); <- this will call the correct updateworld.






Well you still didn't present the case which absolutely requires you to dig up the original type of the BasicGame class. Inheritance is good with dealing such situations where the class implementations differ from each others.

Best regards!

Share this post


Link to post
Share on other sites
Quote:
Any idea why this is being optimised out or is there a way to turn optimisation off?


I am sure a compiler will NEVER optimize a program by omitting a function call that actually does something, and certainly not in debug mode. Are you sure you are using the debugger right? Are you stepping INTO the function (F11 in the VS Debugger) and not just over it?

Have you tried setting a breakpoint within nextWalkFrame() ?

Share this post


Link to post
Share on other sites
Yep, I placed a breakpoint at the top of the function declaration and was stepping into everything. As soon as the program is run the breakpoint turns into the red circle outline with warning symbol. A breakpoint where the function is being called from the other class is reached perfectly fine however.

I don't have access to my code at the minute but the only thing I can think of is that the view is grabbing a local variable instead of the one inside the player class. It's just odd that it chooses once I've got the inheritance tree of game types sorted to stop animating while it worked beforehand (well, in HumanGame, AIGame's animation has never worked due to how it works and that I've not had time). I'll have a look in a few hours when I can access my code

Share this post


Link to post
Share on other sites
"As soon as the program is run the breakpoint turns into the red circle outline with warning symbol."

That typically means that the code doesn't exist in the target. Which implies a compile-time problem, rather than a runtime one.

Is it possible that you have slightly different method signatures? And that therefore instead of your derived method, the compiler is calling your base class's method?

{And therefore that your derived method is simply never being linked into the exe}

Share this post


Link to post
Share on other sites
katie - nextWalkFrame isn't an overridden virtual method, the breakpoint is at the base class function definition. Hovering over the icon says something along the lines of "The code executing will never reach this statement"

Share this post


Link to post
Share on other sites
Stop.

Let's start from the beginning, because there is a lot of confusion in this thread.

This poster is correct, at least this far:

Quote:

I think you're confused (and the 'cplusplus.com' example code is also confused). Your original code:



BasicGame *game;

*game = HumanGame(hInstance, hWnd);

Takes whatever HumanGame() spits out and does a by-value copy of it into storage that hasn't been allocated yet


Look at the expression carefully.

'*game' means "whatever 'game' currently points at". '=' means "should now be assigned". "HumanGame(hInstance, hWnd)" means "an instance of HumanGame".

This is not what you want. For one thing, currently 'game' doesn't point at anything, so "what it points at" can't be assigned anything, because there is nothing to assign to. You must understand here that "assigned" basically means "overwritten with".

For another, if 'game' already points at another type of Game, then assigning a HumanGame to that Game will not change its type to HumanGame. Instead, it will do some kind of conversion. For types that are related by polymorphism, this usually means that the object gets "sliced"; i.e., only the parts of the data specified in the base Game get copied.

What you really want to say is "game should now point at an instance of HumanGame". However, you can't just use a local variable or a temporary, because those won't exist any more after 'createGame' finishes, but the pointer will. To get around this, we can use dynamic allocation.

And you don't really want to assign to a global pointer, either. The purpose of the return value of a function is to return the result of a calculation. The "calculation" performed by a function called "createGame" is to create a Game instance. Therefore, the return value should provide the Game that was created. In other words, have it return the Game*, instead of assigning it somewhere.

Finally, in C++, we use the standard library class std::string to represent text.


Game* createGame(HWND hWnd, HINSTANCE hInstance, const std::string& flags)
{
RECT screen;
GetClientRect(hWnd, &screen);
if (flags == "-ai") // and this is one of the reasons we use std::string.
{
return new HumanGame(hInstance, hWnd);
}
else
{
return new AIGame(hInstance, hWnd);
}
}



If you want to assign to the pointer from somewhere else, you can do so (e.g. 'game = CreateGame(whatever)'). But you should still try to avoid global variables. The natural way to get information to functions is to pass it in the parameters. Scope variables tightly where possible.

Forget about casting for now. It is not relevant to anything you're doing yet.

For the current problem, you need to show more code. In particular, I would like to see the definition for each class.

Share this post


Link to post
Share on other sites
"Finally, in C++, we use the standard library class std::string to represent text."

The only reason I wasn't doing this was that I was taking the parameter directly from the auto-generated VS code.


//BasicGame.h

#ifndef BASICGAME_H
#define BASICGAME_H
#pragma once

#include "Player.h"
#include "Map.h"
#include "ItemFactory.h"

class BasicGame
{

public:

DWORD startDirTime;
Player player;
Map map;

void nextWalkFrame();
bool collision(char x);
bool onSomething(void);
void createInstances(void);
virtual void updateWorld() = 0;
virtual void sanitise() = 0;
Player getPlayer();
Map getMap();

void runRight();
void runLeft();
void jump();
void walkRight();
void walkLeft();
void doNothing();
void gameChecks();

BasicGame(void);
~BasicGame(void);
};
#endif

//HumanGame.h

#ifndef HUMANGAME_H
#define HUMANGAME_H
#include "Player.h"
#include "Keyboard.h"
#include "BasicGame.h"

#pragma once

class Game: public BasicGame
{
private:

char* inputState;
DWORD startDirTime;
Keyboard keyboard;

public:

virtual void updateWorld();
void sanitise();
bool nothingPressed(char* state);
HumanGame(HINSTANCE hInstance, HWND hWnd);
HumanGame(void);
~HumanGame(void);
};
#endif

//AIGame.h

#ifndef AIGAME_H
#define AIGAME_H
#pragma once

#include <iostream>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#include "BasicGame.h"

class AIGame :
public BasicGame
{


public:
void setup(void);
void setupSocket(SOCKET &socket);
void setupWinsock(void);
void bindSocket(SOCKET *socket);
void listenForCon(SOCKET &socket);
void setupClientSocket(SOCKET &socket1, SOCKET &socket2);
void updateWorld(void);
void getX(void);
void getY(void);
void parse(std::string received);
void sanitise(void);
void echo(int x);
AIGame(HINSTANCE hInstance, HWND hWnd);
AIGame(void);
~AIGame(void);
};
#endif





If it was the actual definitions of the functions you were looking for


//BasicGame.cpp


#include "StdAfx.h"
#include "BasicGame.h"


BasicGame::BasicGame(void)
{
}

void BasicGame::createInstances()
{
player.setFrameNumber(0);
player.setPos(D3DXVECTOR3(200, 450, 0.0f));
player.setRight(true);
player.setTopPixel(0);
player.setLastUpdate(timeGetTime());
map.Load("level1\0");


}

bool BasicGame::onSomething()
{

if(map.isSolid(player.getPos().x, player.getPos().y))
{
return true;
}

return false;
}

bool BasicGame::collision(char x)
{

switch(x)
{
case 'u': if(player.getRight())
{
if( map.isSolid(player.getPos().x, player.getPos().y) ||
map.isSolid(player.getPos().x + 10, player.getPos().y))
{return true;}
return false;
}
if(map.isSolid(player.getPos().x - 14, player.getPos().y) ||
map.isSolid(player.getPos().x, player.getPos().y))
{return true;}
return false;

case 'l': if( map.isSolid(player.getPos().x - 10, player.getPos().y + 42) ||
map.isSolid(player.getPos().x - 10, player.getPos().y +3) ||
map.isSolid(player.getPos().x - 10, player.getPos().y +32) )
{return true;}
return false;



case 'r': if( map.isSolid(player.getPos().x + 15, player.getPos().y + 42) ||
map.isSolid(player.getPos().x + 15, player.getPos().y + 3) ||
map.isSolid(player.getPos().x + 15, player.getPos().y +32) )
{return true;}
return false;


case 'd': if(player.getRight())
{
return map.isSolid(player.getPos().x, player.getPos().y + 50);
}
return map.isSolid(player.getPos().x - 5, player.getPos().y + 50);
}
return false;
}

void BasicGame::nextWalkFrame()
{
player.setFrameNumber(player.getFrameNumber() +1);

if(player.getFrameNumber() > 8) // 8 frames of walking animation
{

player.setFrameNumber(0);
}

}
//fix these getters to use references, this isn't java!
//although I don't think they should ever be called.
Player BasicGame::getPlayer()
{
return player;
}

Map BasicGame::getMap()
{
return map;
}

void BasicGame::runLeft()
{
if(player.getRight())
{
player.setRight(false);
player.setFrameNumber(0);
startDirTime = timeGetTime();
}
else
{
DWORD timeInDir = timeGetTime() - startDirTime;
if(timeInDir > 100)
{
nextWalkFrame();

startDirTime = timeGetTime();
}
}

if(player.getPos().x > 21 && !collision('l')) // offset of the sprite, should really #define this
{
player.runLeft();
}

}

void BasicGame::runRight()
{
if(!player.getRight())
{
player.setRight(true);
player.setFrameNumber(0);
startDirTime = timeGetTime();
}
else
{
DWORD timeInDir = timeGetTime() - startDirTime;
if(timeInDir > 100)
{
nextWalkFrame();

startDirTime = timeGetTime();
}
}

if(player.getPos().x < (map.getWidth()*32) - 38) //again offset of the sprite
{

if(!collision('r'))
{
player.runRight();
}
}
}

void BasicGame::walkLeft()
{
if(player.getRight())
{
player.setRight(false);
player.setFrameNumber(0);
startDirTime = timeGetTime();
}
else
{
DWORD timeInDir = timeGetTime() - startDirTime;
if(timeInDir > 100)
{
nextWalkFrame();

startDirTime = timeGetTime();
}
}

if(player.getPos().x > 21 && !collision('l')) // offset of the sprite
{
player.walkLeft();
}
}

void BasicGame::walkRight()
{
if(!player.getRight())
{
player.setRight(true);
player.setFrameNumber(0);
startDirTime = timeGetTime();
}
else
{
DWORD timeInDir = timeGetTime() - startDirTime;
if(timeInDir > 100)
{
nextWalkFrame();

startDirTime = timeGetTime();
}
}

if(player.getPos().x < (map.getWidth() * 32) - 38 && !collision('r'))
{
player.walkRight();
}
}

void BasicGame::jump()
{
if(collision('d'))
{
player.jump();
}
}




void BasicGame::doNothing()
{
if(player.getFrameNumber() != 0)
{
player.setFrameNumber(0);
}

}

void BasicGame::gameChecks()
{
if(player.getPos().y > map.getHeight()* 32)
{
player.lives--;
player.pos = D3DXVECTOR3(200,450,0.0f);
}

if(collision('u') && player.yAccel < 0)
{
player.stopJump();

map.map[player.pos.y/32][player.pos.x/32].hit();
if(player.getRight())
{
map.map[player.pos.y/32][(player.pos.x +3)/32].hit();
}
else
{
map.map[player.pos.y/32][(player.pos.x -5)/32].hit();
}
}

player.update(collision('d'));

}


BasicGame::~BasicGame(void)
{
}



//HumanGame.cpp

//although directInput deprecated for mouse/keyboard using it in this case to get to grips with directInput and future expansion should allow gamepads

#include "StdAfx.h"
#include "HumanGame.h"

#define KEYDOWN(name, key) (name[key] & 0x80)


char* inputState;
DWORD startDirTime;
Keyboard keyboard;


HumanGame::HumanGame(HINSTANCE hInstance, HWND hWnd)
{
keyboard = Keyboard(hInstance, hWnd);
createInstances();

}

void HumanGame::updateWorld()
{
DWORD timeNow = timeGetTime();
RECT screen ;

inputState = keyboard.getInput();

if(inputState != NULL)
{
if(KEYDOWN(inputState, DIK_LEFT) && !KEYDOWN(inputState, DIK_LSHIFT))
{
walkLeft();
}

if(KEYDOWN(inputState, DIK_LEFT) && KEYDOWN(inputState, DIK_LSHIFT))
{
runLeft();
}

if(KEYDOWN(inputState, DIK_RIGHT) && !KEYDOWN(inputState, DIK_LSHIFT))
{
nextWalkFrame();
walkRight();
}

if(KEYDOWN(inputState, DIK_RIGHT) && KEYDOWN(inputState, DIK_LSHIFT))
{
runRight();
}

if(KEYDOWN(inputState, DIK_SPACE))
{
jump();
}

if(nothingPressed(inputState))
{
doNothing();
}
}
HumanGameChecks();

}

bool HumanGame::nothingPressed(char* keyboardState)
{

if(
KEYDOWN(keyboardState, DIK_UP) ||
KEYDOWN(keyboardState, DIK_LEFT) ||
KEYDOWN(keyboardState, DIK_RIGHT)
)
{
return false;
}

return true;
}





void HumanGame::sanitise()
{
keyboard.cleanup();
}


HumanGame::HumanGame(){};

HumanGame::~HumanGame(void)
{
sanitise();
}





//AIGame.cpp

#include "stdafx.h"
#include "AIGame.h"

#define DEFAULT_PORT "25555"
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_BUFLEN 512

char recvbuf[DEFAULT_BUFLEN];
char sendbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
struct addrinfo *result = NULL, hints;
SOCKET clientSock = INVALID_SOCKET;
std::string lastCommand;
struct timeval timeout;
FD_SET read;

AIGame::AIGame(HINSTANCE hInstance, HWND hWnd)
{
createInstances();
setup();
}

AIGame::~AIGame(void)
{
}



void AIGame::getX()
{

sprintf(sendbuf, "%d", player.pos.x);
iResult = send(clientSock, sendbuf, recvbuflen, 0);

}

void AIGame::getY()
{
sprintf(sendbuf, "%d", player.pos.y);
iResult = send(clientSock, sendbuf, recvbuflen, 0);
}


void AIGame::setup()
{


SOCKET listenSocket = INVALID_SOCKET;
setupWinsock();
setupSocket(listenSocket);
bindSocket(&listenSocket);
listenForCon(listenSocket);
FD_ZERO(&read);
FD_SET(listenSocket, &read);
MessageBox(NULL, L"Please launch your AI agent program to connect to \n127.0.0.1 on port 25555, click OK after you have done this", L"Warning!", MB_OK | MB_ICONEXCLAMATION);
setupClientSocket(clientSock, listenSocket);

}

void AIGame::updateWorld()
{
if(select(0,&read,NULL,NULL, &timeout) != 0)
{
iResult = recv(clientSock, recvbuf, recvbuflen, 0);
if (iResult > 0)
{
parse(recvbuf);
}
}
else
{
if(lastCommand.compare("stop") != 0)
{
parse(lastCommand);
}

gameChecks();

}
}

void AIGame::parse(std::string input)
{
if(input.compare("jump") == 0)
{
jump();

}
else if(input.compare("run_right") == 0)
{
lastCommand = "run_right";
runRight();

}
else if(input.compare("run_left") == 0)
{
lastCommand = "run_left";

}
else if(input.compare("walk_right") == 0)
{
lastCommand = "walk_right";
walkRight();

}
else if(input.compare("walk_left") == 0)
{
lastCommand = "walk_left";
walkLeft();

}
else if(input.compare("getX") == 0)
{
getX();
}
else if(input.compare("getY") == 0)
{
getY();
}
else if(input.compare("stop") == 0)
{
lastCommand = "stop";
doNothing();

}
else if(input.compare("exit") == 0)
{

iResult = shutdown(clientSock, SD_SEND);
if (iResult == SOCKET_ERROR)
{
closesocket(clientSock);
WSACleanup();
}
closesocket(clientSock);
WSACleanup();
}
gameChecks();
}

void AIGame::setupWinsock()
{
WORD winsockVersion = MAKEWORD(2, 2);
WSAData wsaData;

WSAStartup(winsockVersion, &wsaData);

}

void AIGame::setupSocket(SOCKET &listenSocket)
{
ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;

int sockErr = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
if(sockErr == 0)
{
listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
}
else
{

WSACleanup();
}
if(listenSocket == INVALID_SOCKET)
{
freeaddrinfo(result);
WSACleanup();

}
}

void AIGame::bindSocket(SOCKET *listenSocket)
{
int bindErr = bind( *listenSocket, result->ai_addr, (int)result->ai_addrlen);
if(bindErr == SOCKET_ERROR)
{
freeaddrinfo(result);
closesocket(*listenSocket);
WSACleanup();
}
freeaddrinfo(result);
timeout.tv_sec = 0;
timeout.tv_usec = 3333;
setsockopt(*listenSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
}

void AIGame::setupClientSocket(SOCKET &clientSock, SOCKET &listenSocket)
{
clientSock = accept(listenSocket, NULL, NULL);
if(clientSock == INVALID_SOCKET)
{

WSACleanup();
closesocket(listenSocket);
}

}

void AIGame::listenForCon(SOCKET &listenSocket)
{

int listenErr = listen(listenSocket, SOMAXCONN);
if (listenErr == SOCKET_ERROR)
{
closesocket(listenSocket);
WSACleanup();

}
}

void AIGame::echo(int x)
{
send(clientSock, (char*)x,sizeof((char*)x),0);
}

void AIGame::sanitise(){} //no longer really required




In this case the "slicing" of the BaseGame to form an instance of HumanGame doesn't appear to cause any problems, I used Jansic's idea and am currently using:

if(strcmp(flags, "-ai") != 0)
{
game = new Game(hInstance, hWnd);
}
else
{
game = new AIGame(hInstance, hWnd);
}


I know there is a lot that needs rewritten in this (using getters rather than giving everything direct access being a big part, still getting my head round passing by reference rather than value (damn Java background!))

On the same note as my later posts about the problems with animation - calls of the BasicGame class work up until gameChecks() gets called, at which point player and map are fine, but as soon as the program enters the switch case of collision(char x) everything suddenly shows "CXX0030: Error: expression cannot be evaluated"

Sorry for the long-winded post, and thanks a lot Zahlman, as usual you're being incredibly helpful!

Share this post


Link to post
Share on other sites

This topic is 2847 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this