Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
Seminar-HFO
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Shashank Suhas
Seminar-HFO
Commits
08e41376
Commit
08e41376
authored
Jul 08, 2015
by
Matthew Hausknecht
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added support for recording of non-player agents.
parent
37af3268
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
175 additions
and
29 deletions
+175
-29
CMakeLists.txt
CMakeLists.txt
+1
-1
bin/Trainer.py
bin/Trainer.py
+10
-0
src/HFO.cpp
src/HFO.cpp
+47
-0
src/HFO.hpp
src/HFO.hpp
+17
-0
src/agent.cpp
src/agent.cpp
+20
-20
src/agent.h
src/agent.h
+10
-6
src/sample_player.cpp
src/sample_player.cpp
+58
-2
src/sample_player.h
src/sample_player.h
+12
-0
No files found.
CMakeLists.txt
View file @
08e41376
...
@@ -35,7 +35,7 @@ list(APPEND LINK_LIBS
...
@@ -35,7 +35,7 @@ list(APPEND LINK_LIBS
)
)
add_executable
(
sample_coach
${
SOURCE_DIR
}
/main_coach.cpp
${
SOURCE_DIR
}
/sample_coach.cpp
${
SOURCES
}
)
add_executable
(
sample_coach
${
SOURCE_DIR
}
/main_coach.cpp
${
SOURCE_DIR
}
/sample_coach.cpp
${
SOURCES
}
)
add_executable
(
sample_player
${
SOURCE_DIR
}
/main_player.cpp
${
SOURCE_DIR
}
/sample_player.cpp
${
SOURCES
}
)
add_executable
(
sample_player
${
SOURCE_DIR
}
/main_player.cpp
${
SOURCE_DIR
}
/sample_player.cpp
${
SOURCES
}
${
SOURCE_DIR
}
/agent.cpp
)
add_executable
(
sample_trainer
${
SOURCE_DIR
}
/main_trainer.cpp
${
SOURCE_DIR
}
/sample_trainer.cpp
${
SOURCES
}
)
add_executable
(
sample_trainer
${
SOURCE_DIR
}
/main_trainer.cpp
${
SOURCE_DIR
}
/sample_trainer.cpp
${
SOURCES
}
)
add_executable
(
agent
${
SOURCE_DIR
}
/main_agent.cpp
${
SOURCE_DIR
}
/agent.cpp
${
SOURCES
}
)
add_executable
(
agent
${
SOURCE_DIR
}
/main_agent.cpp
${
SOURCE_DIR
}
/agent.cpp
${
SOURCES
}
)
add_library
(
hfo-lib SHARED
${
SOURCE_DIR
}
/HFO.hpp
${
SOURCE_DIR
}
/HFO.cpp
)
add_library
(
hfo-lib SHARED
${
SOURCE_DIR
}
/HFO.hpp
${
SOURCE_DIR
}
/HFO.cpp
)
...
...
bin/Trainer.py
View file @
08e41376
...
@@ -659,6 +659,16 @@ class Trainer(object):
...
@@ -659,6 +659,16 @@ class Trainer(object):
agent
=
self
.
launch_agent
(
agent_num
,
play_offense
=
False
,
port
=
port
)
agent
=
self
.
launch_agent
(
agent_num
,
play_offense
=
False
,
port
=
port
)
self
.
_agentPopen
.
append
(
agent
)
self
.
_agentPopen
.
append
(
agent
)
necProcesses
.
append
([
agent
,
'defense_agent_'
+
str
(
agent_num
)])
necProcesses
.
append
([
agent
,
'defense_agent_'
+
str
(
agent_num
)])
# Broadcast the HFO configuration
offense_nums
=
' '
.
join
([
str
(
self
.
convertToExtPlayer
(
self
.
_offenseTeamName
,
i
))
for
i
in
xrange
(
1
,
self
.
_numOffense
+
1
)])
defense_nums
=
' '
.
join
([
str
(
self
.
convertToExtPlayer
(
self
.
_defenseTeamName
,
i
))
for
i
in
xrange
(
self
.
_numDefense
)])
self
.
send
(
'(say HFO_SETUP offense_name
%
s defense_name
%
s num_offense
%
d'
\
' num_defense
%
d offense_nums
%
s defense_nums
%
s)'
%
(
self
.
_offenseTeamName
,
self
.
_defenseTeamName
,
self
.
_numOffense
,
self
.
_numDefense
,
offense_nums
,
defense_nums
))
self
.
startGame
()
self
.
startGame
()
while
self
.
checkLive
(
necProcesses
):
while
self
.
checkLive
(
necProcesses
):
prevFrame
=
self
.
_frame
prevFrame
=
self
.
_frame
...
...
src/HFO.cpp
View file @
08e41376
...
@@ -6,8 +6,55 @@
...
@@ -6,8 +6,55 @@
#include <sys/types.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <assert.h>
#include <netdb.h>
#include <netdb.h>
#include <iostream>
#include <iostream>
#include <sstream>
bool
HFOEnvironment
::
ParseHFOConfig
(
const
std
::
string
&
message
,
HFO_Config
&
config
)
{
config
.
num_offense
=
-
1
;
config
.
num_defense
=
-
1
;
std
::
istringstream
iss
(
message
);
std
::
string
header
=
"HFO_SETUP"
;
std
::
string
key
,
val
;
iss
>>
key
;
if
(
header
.
compare
(
key
)
!=
0
)
{
std
::
cerr
<<
"Got unexpected message header: "
<<
header
;
return
false
;
}
while
(
iss
>>
key
)
{
if
(
key
.
compare
(
"offense_name"
)
==
0
)
{
iss
>>
config
.
offense_team_name
;
}
else
if
(
key
.
compare
(
"defense_name"
)
==
0
)
{
iss
>>
config
.
defense_team_name
;
}
else
if
(
key
.
compare
(
"num_offense"
)
==
0
)
{
iss
>>
val
;
config
.
num_offense
=
strtol
(
val
.
c_str
(),
NULL
,
0
);
}
else
if
(
key
.
compare
(
"num_defense"
)
==
0
)
{
iss
>>
val
;
config
.
num_defense
=
strtol
(
val
.
c_str
(),
NULL
,
0
);
}
else
if
(
key
.
compare
(
"offense_nums"
)
==
0
)
{
assert
(
config
.
num_offense
>=
0
);
for
(
int
i
=
0
;
i
<
config
.
num_offense
;
++
i
)
{
iss
>>
val
;
config
.
offense_nums
.
push_back
(
strtol
(
val
.
c_str
(),
NULL
,
0
));
}
}
else
if
(
key
.
compare
(
"defense_nums"
)
==
0
)
{
assert
(
config
.
num_defense
>=
0
);
for
(
int
i
=
0
;
i
<
config
.
num_defense
;
++
i
)
{
iss
>>
val
;
config
.
defense_nums
.
push_back
(
strtol
(
val
.
c_str
(),
NULL
,
0
));
}
}
else
{
std
::
cerr
<<
"Unrecognized key: "
<<
key
<<
std
::
endl
;
return
false
;
}
}
assert
(
config
.
offense_nums
.
size
()
==
config
.
num_offense
);
assert
(
config
.
defense_nums
.
size
()
==
config
.
num_defense
);
return
true
;
};
void
error
(
const
char
*
msg
)
{
void
error
(
const
char
*
msg
)
{
perror
(
msg
);
perror
(
msg
);
...
...
src/HFO.hpp
View file @
08e41376
#ifndef __HFO_HPP__
#ifndef __HFO_HPP__
#define __HFO_HPP__
#define __HFO_HPP__
#include <string>
#include <vector>
#include <vector>
// For descriptions of the different feature sets see
// For descriptions of the different feature sets see
...
@@ -41,11 +42,27 @@ struct Action {
...
@@ -41,11 +42,27 @@ struct Action {
float
arg2
;
float
arg2
;
};
};
// Configuration of the HFO domain including the team names and player
// numbers for each team. This can be populated by ParseHFOConfig().
struct
HFO_Config
{
std
::
string
offense_team_name
;
std
::
string
defense_team_name
;
int
num_offense
;
// Number of offensive players
int
num_defense
;
// Number of defensive players
std
::
vector
<
int
>
offense_nums
;
// Offensive player numbers
std
::
vector
<
int
>
defense_nums
;
// Defensive player numbers
};
class
HFOEnvironment
{
class
HFOEnvironment
{
public:
public:
HFOEnvironment
();
HFOEnvironment
();
~
HFOEnvironment
();
~
HFOEnvironment
();
// Parse a message sent from Trainer to construct an HFO config.
// Returns a bool indicating if the struct was correctly parsed.
static
bool
ParseHFOConfig
(
const
std
::
string
&
message
,
HFO_Config
&
config
);
// Connect to the server that controls the agent on the specified port.
// Connect to the server that controls the agent on the specified port.
void
connectToAgentServer
(
int
server_port
=
6000
,
void
connectToAgentServer
(
int
server_port
=
6000
,
feature_set_t
feature_set
=
HIGH_LEVEL_FEATURE_SET
);
feature_set_t
feature_set
=
HIGH_LEVEL_FEATURE_SET
);
...
...
src/agent.cpp
View file @
08e41376
...
@@ -29,6 +29,7 @@
...
@@ -29,6 +29,7 @@
#endif
#endif
#include "agent.h"
#include "agent.h"
#include "HFO.cpp"
#include "strategy.h"
#include "strategy.h"
#include "field_analyzer.h"
#include "field_analyzer.h"
...
@@ -112,13 +113,6 @@ using namespace rcsc;
...
@@ -112,13 +113,6 @@ using namespace rcsc;
float
min_feat_val
=
1e8
;
float
min_feat_val
=
1e8
;
float
max_feat_val
=
-
1e8
;
float
max_feat_val
=
-
1e8
;
// Socket Error
void
error
(
const
char
*
msg
)
{
perror
(
msg
);
exit
(
1
);
}
// Minimium and feature values
// Minimium and feature values
#define FEAT_MIN -1.
#define FEAT_MIN -1.
#define FEAT_MAX 1.
#define FEAT_MAX 1.
...
@@ -296,7 +290,11 @@ void Agent::clientHandshake() {
...
@@ -296,7 +290,11 @@ void Agent::clientHandshake() {
error
(
"[Agent Server] ERROR recv from socket"
);
error
(
"[Agent Server] ERROR recv from socket"
);
}
}
// Create the corresponding FeatureExtractor
// Create the corresponding FeatureExtractor
feature_extractor
=
getFeatureExtractor
(
feature_set
);
if
(
feature_extractor
!=
NULL
)
{
delete
feature_extractor
;
}
feature_extractor
=
getFeatureExtractor
(
feature_set
,
num_teammates
,
num_opponents
,
playing_offense
);
// Send the number of features
// Send the number of features
int
numFeatures
=
feature_extractor
->
getNumFeatures
();
int
numFeatures
=
feature_extractor
->
getNumFeatures
();
assert
(
numFeatures
>
0
);
assert
(
numFeatures
>
0
);
...
@@ -314,11 +312,10 @@ void Agent::clientHandshake() {
...
@@ -314,11 +312,10 @@ void Agent::clientHandshake() {
std
::
cout
<<
"[Agent Server] Handshake complete"
<<
std
::
endl
;
std
::
cout
<<
"[Agent Server] Handshake complete"
<<
std
::
endl
;
}
}
FeatureExtractor
*
Agent
::
getFeatureExtractor
(
feature_set_t
feature_set_indx
)
{
FeatureExtractor
*
Agent
::
getFeatureExtractor
(
feature_set_t
feature_set_indx
,
if
(
feature_extractor
!=
NULL
)
{
int
num_teammates
,
delete
feature_extractor
;
int
num_opponents
,
}
bool
playing_offense
)
{
switch
(
feature_set_indx
)
{
switch
(
feature_set_indx
)
{
case
LOW_LEVEL_FEATURE_SET
:
case
LOW_LEVEL_FEATURE_SET
:
return
new
LowLevelFeatureExtractor
(
num_teammates
,
num_opponents
,
return
new
LowLevelFeatureExtractor
(
num_teammates
,
num_opponents
,
...
@@ -335,11 +332,12 @@ FeatureExtractor* Agent::getFeatureExtractor(feature_set_t feature_set_indx) {
...
@@ -335,11 +332,12 @@ FeatureExtractor* Agent::getFeatureExtractor(feature_set_t feature_set_indx) {
}
}
}
}
hfo_status_t
Agent
::
getGameStatus
()
{
hfo_status_t
Agent
::
getGameStatus
(
const
rcsc
::
AudioSensor
&
audio_sensor
,
long
&
lastTrainerMessageTime
)
{
hfo_status_t
game_status
=
IN_GAME
;
hfo_status_t
game_status
=
IN_GAME
;
if
(
audio
Sensor
()
.
trainerMessageTime
().
cycle
()
>
lastTrainerMessageTime
)
{
if
(
audio
_sensor
.
trainerMessageTime
().
cycle
()
>
lastTrainerMessageTime
)
{
lastTrainerMessageTime
=
audioSensor
().
trainerMessageTime
().
cycl
e
();
const
std
::
string
&
message
=
audio_sensor
.
trainerMessag
e
();
const
std
::
string
&
message
=
audioSensor
().
trainerMessage
()
;
bool
recognized_message
=
true
;
if
(
message
.
compare
(
"GOAL"
)
==
0
)
{
if
(
message
.
compare
(
"GOAL"
)
==
0
)
{
game_status
=
GOAL
;
game_status
=
GOAL
;
}
else
if
(
message
.
compare
(
"CAPTURED_BY_DEFENSE"
)
==
0
)
{
}
else
if
(
message
.
compare
(
"CAPTURED_BY_DEFENSE"
)
==
0
)
{
...
@@ -349,8 +347,10 @@ hfo_status_t Agent::getGameStatus() {
...
@@ -349,8 +347,10 @@ hfo_status_t Agent::getGameStatus() {
}
else
if
(
message
.
compare
(
"OUT_OF_TIME"
)
==
0
)
{
}
else
if
(
message
.
compare
(
"OUT_OF_TIME"
)
==
0
)
{
game_status
=
OUT_OF_TIME
;
game_status
=
OUT_OF_TIME
;
}
else
{
}
else
{
std
::
cout
<<
"[Agent Server] Unrecognized Trainer Message: "
<<
message
recognized_message
=
false
;
<<
std
::
endl
;
}
if
(
recognized_message
)
{
lastTrainerMessageTime
=
audio_sensor
.
trainerMessageTime
().
cycle
();
}
}
}
}
return
game_status
;
return
game_status
;
...
@@ -367,7 +367,7 @@ void Agent::actionImpl() {
...
@@ -367,7 +367,7 @@ void Agent::actionImpl() {
}
}
// Update and send the game status
// Update and send the game status
hfo_status_t
game_status
=
getGameStatus
();
hfo_status_t
game_status
=
getGameStatus
(
audioSensor
(),
lastTrainerMessageTime
);
if
(
send
(
newsockfd
,
&
game_status
,
sizeof
(
int
),
0
)
<
0
)
{
if
(
send
(
newsockfd
,
&
game_status
,
sizeof
(
int
),
0
)
<
0
)
{
error
(
"[Agent Server] ERROR sending from socket"
);
error
(
"[Agent Server] ERROR sending from socket"
);
}
}
...
...
src/agent.h
View file @
08e41376
...
@@ -42,6 +42,16 @@ public:
...
@@ -42,6 +42,16 @@ public:
virtual
~
Agent
();
virtual
~
Agent
();
virtual
FieldEvaluator
::
ConstPtr
getFieldEvaluator
()
const
;
virtual
FieldEvaluator
::
ConstPtr
getFieldEvaluator
()
const
;
// Get the current game status
static
hfo_status_t
getGameStatus
(
const
rcsc
::
AudioSensor
&
audio_sensor
,
long
&
lastTrainerMessageTime
);
// Returns the feature extractor corresponding to the feature_set_t
static
FeatureExtractor
*
getFeatureExtractor
(
feature_set_t
feature_set
,
int
num_teammates
,
int
num_opponents
,
bool
playing_offense
);
protected:
protected:
// You can override this method. But you must call
// You can override this method. But you must call
// PlayerAgent::initImpl() in this method.
// PlayerAgent::initImpl() in this method.
...
@@ -60,18 +70,12 @@ protected:
...
@@ -60,18 +70,12 @@ protected:
virtual
FieldEvaluator
::
ConstPtr
createFieldEvaluator
()
const
;
virtual
FieldEvaluator
::
ConstPtr
createFieldEvaluator
()
const
;
virtual
ActionGenerator
::
ConstPtr
createActionGenerator
()
const
;
virtual
ActionGenerator
::
ConstPtr
createActionGenerator
()
const
;
// Get the current game status
hfo_status_t
getGameStatus
();
// Start the server and listen for a connection.
// Start the server and listen for a connection.
void
startServer
(
int
server_port
=
6008
);
void
startServer
(
int
server_port
=
6008
);
// Transmit information to the client and ensure it can recieve.
// Transmit information to the client and ensure it can recieve.
void
clientHandshake
();
void
clientHandshake
();
// Returns the feature extractor corresponding to the feature_set_t
FeatureExtractor
*
getFeatureExtractor
(
feature_set_t
feature_set
);
protected:
protected:
FeatureExtractor
*
feature_extractor
;
FeatureExtractor
*
feature_extractor
;
long
lastTrainerMessageTime
;
// Last time the trainer sent a message
long
lastTrainerMessageTime
;
// Last time the trainer sent a message
...
...
src/sample_player.cpp
View file @
08e41376
...
@@ -29,6 +29,7 @@
...
@@ -29,6 +29,7 @@
#endif
#endif
#include "sample_player.h"
#include "sample_player.h"
#include "agent.h"
#include "strategy.h"
#include "strategy.h"
#include "field_analyzer.h"
#include "field_analyzer.h"
...
@@ -52,6 +53,8 @@
...
@@ -52,6 +53,8 @@
#include "view_tactical.h"
#include "view_tactical.h"
#include "intention_receive.h"
#include "intention_receive.h"
#include "lowlevel_feature_extractor.h"
#include "highlevel_feature_extractor.h"
#include <rcsc/action/basic_actions.h>
#include <rcsc/action/basic_actions.h>
#include <rcsc/action/bhv_emergency.h>
#include <rcsc/action/bhv_emergency.h>
...
@@ -95,7 +98,12 @@ SamplePlayer::SamplePlayer()
...
@@ -95,7 +98,12 @@ SamplePlayer::SamplePlayer()
:
PlayerAgent
(),
:
PlayerAgent
(),
M_communication
(),
M_communication
(),
M_field_evaluator
(
createFieldEvaluator
()
),
M_field_evaluator
(
createFieldEvaluator
()
),
M_action_generator
(
createActionGenerator
()
)
M_action_generator
(
createActionGenerator
()
),
feature_extractor
(
NULL
),
lastTrainerMessageTime
(
-
1
),
num_teammates
(
-
1
),
num_opponents
(
-
1
),
playing_offense
(
false
)
{
{
boost
::
shared_ptr
<
AudioMemory
>
audio_memory
(
new
AudioMemory
);
boost
::
shared_ptr
<
AudioMemory
>
audio_memory
(
new
AudioMemory
);
...
@@ -157,7 +165,9 @@ SamplePlayer::SamplePlayer()
...
@@ -157,7 +165,9 @@ SamplePlayer::SamplePlayer()
*/
*/
SamplePlayer
::~
SamplePlayer
()
SamplePlayer
::~
SamplePlayer
()
{
{
if
(
feature_extractor
!=
NULL
)
{
delete
feature_extractor
;
}
}
}
/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/
...
@@ -215,6 +225,34 @@ SamplePlayer::initImpl( CmdLineParser & cmd_parser )
...
@@ -215,6 +225,34 @@ SamplePlayer::initImpl( CmdLineParser & cmd_parser )
return
true
;
return
true
;
}
}
/*! Listen from a message from the trainer that reveals the
configuration of the HFO domain. Use this to populate our HFO_Config
struct.
*/
bool
SamplePlayer
::
getHFOConfig
()
{
const
AudioSensor
&
audio_sensor
=
audioSensor
();
if
(
audio_sensor
.
trainerMessageTime
().
cycle
()
>
lastTrainerMessageTime
)
{
const
std
::
string
&
message
=
audio_sensor
.
trainerMessage
();
if
(
HFOEnvironment
::
ParseHFOConfig
(
message
,
hfo_config
))
{
lastTrainerMessageTime
=
audio_sensor
.
trainerMessageTime
().
cycle
();
if
(
config
().
teamName
().
compare
(
hfo_config
.
offense_team_name
)
==
0
)
{
playing_offense
=
true
;
}
else
if
(
config
().
teamName
().
compare
(
hfo_config
.
defense_team_name
)
==
0
)
{
playing_offense
=
false
;
}
if
(
playing_offense
)
{
num_teammates
=
std
::
max
(
0
,
hfo_config
.
num_offense
-
1
);
num_opponents
=
hfo_config
.
num_defense
;
}
else
{
num_teammates
=
std
::
max
(
0
,
hfo_config
.
num_defense
-
1
);
num_opponents
=
hfo_config
.
num_offense
;
}
return
true
;
}
}
return
false
;
}
/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/
/*!
/*!
main decision
main decision
...
@@ -223,6 +261,24 @@ SamplePlayer::initImpl( CmdLineParser & cmd_parser )
...
@@ -223,6 +261,24 @@ SamplePlayer::initImpl( CmdLineParser & cmd_parser )
void
void
SamplePlayer
::
actionImpl
()
SamplePlayer
::
actionImpl
()
{
{
#ifdef ELOG
if
(
config
().
record
())
{
if
(
feature_extractor
==
NULL
)
{
if
(
getHFOConfig
())
{
feature_extractor
=
Agent
::
getFeatureExtractor
(
LOW_LEVEL_FEATURE_SET
,
num_teammates
,
num_opponents
,
playing_offense
);
}
}
else
{
hfo_status_t
game_status
=
Agent
::
getGameStatus
(
audioSensor
(),
lastTrainerMessageTime
);
elog
.
addText
(
Logger
::
WORLD
,
"GameStatus %d"
,
game_status
);
elog
.
flush
();
feature_extractor
->
ExtractFeatures
(
this
->
world
());
feature_extractor
->
LogFeatures
();
}
}
#endif
//
//
// update strategy and analyzer
// update strategy and analyzer
//
//
...
...
src/sample_player.h
View file @
08e41376
...
@@ -27,9 +27,11 @@
...
@@ -27,9 +27,11 @@
#ifndef SAMPLE_PLAYER_H
#ifndef SAMPLE_PLAYER_H
#define SAMPLE_PLAYER_H
#define SAMPLE_PLAYER_H
#include "HFO.hpp"
#include "action_generator.h"
#include "action_generator.h"
#include "field_evaluator.h"
#include "field_evaluator.h"
#include "communication.h"
#include "communication.h"
#include "feature_extractor.h"
#include <rcsc/player/player_agent.h>
#include <rcsc/player/player_agent.h>
#include <vector>
#include <vector>
...
@@ -95,6 +97,16 @@ private:
...
@@ -95,6 +97,16 @@ private:
public:
public:
virtual
virtual
FieldEvaluator
::
ConstPtr
getFieldEvaluator
()
const
;
FieldEvaluator
::
ConstPtr
getFieldEvaluator
()
const
;
protected:
// Listens for a HFO Config message
bool
getHFOConfig
();
HFO_Config
hfo_config
;
FeatureExtractor
*
feature_extractor
;
long
lastTrainerMessageTime
;
int
num_teammates
,
num_opponents
;
bool
playing_offense
;
};
};
#endif
#endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment