/*-----------------------------------------------------------------------------
M o v e r C o n t r o l
Copyright 2007 Activeworlds Inc
Licensed Material -- Program Property of Activeworlds Inc.
-----------------------------------------------------------------------------*/
#define MAX_MODEL_NAME 64
#define WAIT_TIME 10 // time to wait for SDK packets and events before update
#define WAYPOINT_RESOLUTION 1 // accuracy, interpolates waypoint (1 to 10) - use as low as possible!
#define UPDATE_INTERVAL (1000 / (aw_int (AW_WORLD_AVATAR_REFRESH_RATE) * WAYPOINT_RESOLUTION))
#define IDLE_INTERVAL ((aw_int (AW_WORLD_MOVER_USED_RESET_TIMEOUT) * 1000) / 2)
#define STATE_INVALIDATE -1 // invalidate mover's state
#define MAX_WAYPOINTS 16 // number of elements in "waypoints" array
// announced messages
#define START_MSG "Bus started."
#define STOP_MSG "Bus stopped. Please leave the bus."
#define READY_MSG "Passengers are welcome! Bus will start in 15 seconds."
#define POSTPONE_MSG "Tour postponed by 15 seconds. Passengers are welcome!"
#define RIDER_ADDED_WHISPER "You joined the bus tour."
#define RIDER_DELETED_WHISPER "You left the bus tour."
#include "aw.h"
#include <windows.h>
#include <stdio.h>
#include <time.h>
struct {
int x;
int y;
int z;
int yaw;
int pitch;
int roll;
} waypoints[MAX_WAYPOINTS] = {
// X Y Z YAW PITCH ROLL
{0, 0, 0, 0, 0, 0},
{0, 100, 500, 0, -16, 32},
{500, 400, 1000, 160, -32, 64},
{1000, 300, 1500, 280, -16, 128},
{1500, 200, 2000, 360, -16, 64},
{2000, 0, 2000, 450, 0, -128},
{1000, 200, 2500, -450, -16, -256},
{500, 400, 3000, -300, -16, -64},
{0, 900, 3500, -215, -32, -32},
{0, 400, 4000, 0, -32, 0},
{0, 0, 4200, 0, -16, 0},
{0, 900, 4000, 900, -180, 180},
{500, 1500, 2000, 2400, -360, 360},
{-1500, 900, 500, 1200, 180, -90},
{-800, 400, -900, 0, 32, -32},
{0, 0, 0, 0, 0, 0},
};
struct rider {
int session;
struct rider* next;
};
struct mover {
unsigned int id;
int state;
int init_x;
int init_y;
int init_z;
int init_yaw;
int init_pitch;
int init_roll;
int cur_x;
int cur_y;
int cur_z;
int cur_yaw;
int cur_pitch;
int cur_roll;
unsigned int start;
unsigned int last_update;
unsigned int last_move;
float last_waypoint;
int rider_count;
struct rider* riders;
struct mover* next;
};
static struct mover* movers = NULL;
static int mv_owner = 0;
static char mv_model[MAX_MODEL_NAME];
void log_msg (char* format, ...)
{
va_list args;
struct tm* now;
time_t secs;
char buffer[1024];
time (&secs);
now = localtime (&secs);
strftime (buffer, sizeof (buffer), "%y-%m-%d %X ", now);
printf (buffer);
va_start (args, format);
__try {
vsprintf (buffer, format, args);
}
__except( EXCEPTION_EXECUTE_HANDLER ) {
return;
}
va_end (args);
puts (buffer);
}
void announce (char* format, ...)
{
va_list args;
char buffer[1024];
va_start (args, format);
__try {
vsprintf (buffer, format, args);
}
__except( EXCEPTION_EXECUTE_HANDLER ) {
return;
}
va_end (args);
aw_say (buffer);
log_msg ("announce : \"%s\"", buffer);
}
BOOL rider_add (unsigned int id, int session)
{
struct mover* mv;
struct rider* rd;
BOOL exists;
if (session == aw_session ())
return TRUE; // dont add own session
for (mv = movers; mv; mv = mv->next)
if (mv->id == id)
break;
if (!mv)
return FALSE;
for (exists = FALSE, rd = mv->riders; rd; rd = rd->next)
if (rd->session == session) {
exists = TRUE;
break; // exists already
}
if (!rd
&& !(rd = (struct rider*)calloc (1, sizeof (struct rider))))
return FALSE;
rd->session = session;
if (!exists) {
if (mv->riders)
rd->next = mv->riders;
mv->riders = rd;
mv->rider_count++;
aw_whisper (session, RIDER_ADDED_WHISPER);
}
return TRUE;
}
BOOL rider_del (unsigned int id, int session)
{
struct mover* mv;
struct rider* rd;
struct rider* prev;
for (mv = movers; mv; mv = mv->next)
if (mv->id == id)
break;
if (!mv)
return FALSE; // mover does not exist
for (prev = NULL, rd = mv->riders; rd; rd = rd->next) {
if (rd->session == session)
break;
prev = rd;
}
if (!rd)
return FALSE;
if (prev)
prev->next = rd->next;
else
movers->riders = rd->next;
free (rd);
mv->rider_count--;
aw_whisper (session, RIDER_DELETED_WHISPER);
return TRUE;
}
BOOL mover_add (unsigned int id, int state, int x, int y, int z,
int yaw, int pitch, int roll)
{
struct mover* mv;
BOOL exists;
if (movers)
return FALSE; // this bot controls one mover only!
for (exists = FALSE, mv = movers; mv; mv = mv->next)
if (mv->id == id) {
exists = TRUE;
break; // exists already
}
if (!mv
&& !(mv = (struct mover*)calloc (1, sizeof (struct mover))))
return FALSE;
mv->id = id;
mv->init_x = x;
mv->init_y = y;
mv->init_z = z;
mv->init_yaw = yaw;
mv->init_pitch = pitch;
mv->init_roll = roll;
mv->cur_x = waypoints[0].x;
mv->cur_y = waypoints[0].y;
mv->cur_z = waypoints[0].z;
mv->cur_yaw = waypoints[0].yaw;
mv->cur_pitch = waypoints[0].pitch;
mv->cur_roll = waypoints[0].roll;
mv->last_waypoint = 0.0f;
mv->start = aw_tick ();
mv->state = state;
if (!exists) {
if (movers)
mv->next = movers;
movers = mv;
}
return TRUE;
}
BOOL mover_change (unsigned int id, int state, int x, int y, int z,
int yaw, int pitch, int roll)
{
struct mover* mv;
for (mv = movers; mv; mv = mv->next)
if (mv->id == id)
break;
if (!mv)
return FALSE;
mv->state = state;
if (state == AW_MOVER_STATE_RESET) {
mv->cur_x = waypoints[0].x;
mv->cur_y = waypoints[0].y;
mv->cur_z = waypoints[0].z;
mv->cur_yaw = waypoints[0].yaw;
mv->cur_pitch = waypoints[0].pitch;
mv->cur_roll = waypoints[0].roll;
mv->last_waypoint = 0.0f;
mv->start = aw_tick ();
}
return TRUE;
}
BOOL mover_del (unsigned int id)
{
struct mover* mv;
struct mover* prev;
struct rider* rd;
struct rider* rd_tmp;
for (prev = NULL, mv = movers; mv; mv = mv->next) {
if (mv->id == id)
break;
prev = mv;
}
if (!mv)
return FALSE;
for (rd = mv->riders; rd; ) {
rd_tmp = rd;
rd = rd->next;
free (rd_tmp);
}
mv->riders = NULL;
if (prev)
prev->next = mv->next;
else
movers = mv->next;
free (mv);
return TRUE;
}
void reset ()
{
struct mover* mv;
struct mover* mv_tmp;
struct rider* rd;
struct rider* rd_tmp;
for (mv = movers; mv; ) {
for (rd = mv->riders; rd; ) {
rd_tmp = rd;
rd = rd->next;
free (rd_tmp);
}
mv_tmp = mv;
mv = mv->next;
free (mv_tmp);
}
movers = NULL;
}
void handle_entity_add (void)
{
if (aw_int (AW_ENTITY_TYPE) == AW_ENTITY_MOVER
&& aw_int (AW_ENTITY_ID)
&& aw_int (AW_ENTITY_OWNER_CITIZEN) == mv_owner
&& !stricmp (aw_string (AW_OBJECT_MODEL), mv_model)) {
if (mover_add (aw_int (AW_ENTITY_ID), aw_int (AW_ENTITY_STATE),
aw_int (AW_OBJECT_X), aw_int (AW_OBJECT_Y), aw_int (AW_OBJECT_Z),
aw_int (AW_OBJECT_YAW), aw_int (AW_OBJECT_TILT), aw_int (AW_OBJECT_ROLL))) {
// Reset the mover in case it's used (causes mover to blick 10 seconds)
if (aw_int (AW_ENTITY_STATE) != AW_MOVER_STATE_IDLE)
aw_mover_set_state (aw_int (AW_ENTITY_ID), AW_MOVER_STATE_RESET, 0);
log_msg ("Mover ADD: %u %s", aw_int (AW_ENTITY_ID), aw_string (AW_OBJECT_MODEL));
}
}
}
void handle_entity_change (void)
{
if (mover_change (aw_int (AW_ENTITY_ID), aw_int (AW_ENTITY_STATE),
aw_int (AW_ENTITY_X), aw_int (AW_ENTITY_Y), aw_int (AW_ENTITY_Z),
aw_int (AW_ENTITY_YAW), aw_int (AW_ENTITY_PITCH), aw_int (AW_ENTITY_ROLL))) {
// Reset the mover when bot isn't owner! (somewhere else is using it)
if (aw_int (AW_ENTITY_STATE) != AW_MOVER_STATE_RESET
&& aw_int (AW_ENTITY_STATE) != AW_MOVER_STATE_IDLE
&& aw_int (AW_ENTITY_OWNER_SESSION) != aw_session ())
aw_mover_set_state (aw_int (AW_ENTITY_ID), AW_MOVER_STATE_RESET, 0);
log_msg ("Mover CHG: %u state: %d", aw_int (AW_ENTITY_ID), aw_int (AW_ENTITY_STATE));
}
}
void handle_entity_delete (void)
{
if (mover_del (aw_int (AW_ENTITY_ID)))
log_msg ("Mover DEL: %u", aw_int (AW_ENTITY_ID));
}
void handle_entity_rider_add (void)
{
if (rider_add (aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION)))
log_msg ("Rider ADD: %u session# %d", aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION));
}
void handle_entity_rider_change (void)
{
if (rider_add (aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION)))
log_msg ("Rider CHG: %u session# %d", aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION));
}
void handle_entity_rider_delete (void)
{
if (rider_del (aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION)))
log_msg ("Rider DEL: %u session# %d", aw_int (AW_ENTITY_ID), aw_int (AW_AVATAR_SESSION));
}
void handle_enter (int rc)
{
reset ();
if (rc) {
log_msg ("world enter failed [%d]", rc);
return;
}
// announce our position in the world
aw_int_set (AW_MY_X, 0); // 0W
aw_int_set (AW_MY_Y, 0); // 0a
aw_int_set (AW_MY_Z, 0); // 0N
aw_int_set (AW_MY_YAW, 0); // facing south
aw_int_set (AW_MY_PITCH, 0);
if (rc = aw_state_change ()) {
log_msg ("Unable to change state (reason %d)", rc);
exit (1);
}
log_msg ("%s entered world %s",
aw_string (AW_LOGIN_NAME), aw_string (AW_WORLD_NAME));
}
void handle_world_disconnect (void)
{
reset ();
log_msg ("world disconnected [%d]", aw_int (AW_DISCONNECT_REASON));
}
void pause (int id)
{
aw_mover_set_state (id, AW_MOVER_STATE_PAUSE, 0);
}
void start (int id)
{
struct mover* mv;
struct rider* rd;
int num_riders;
int angle;
aw_mover_set_state (id, AW_MOVER_STATE_CONTINUE, 0);
// optionally position all riders to their seats,
// starting with world build 86 and browser build 969
for (mv = movers; mv; mv = mv->next)
if (mv->id == (unsigned int)id)
break;
if (!mv)
return;
for (num_riders = 0, rd = mv->riders; rd; rd = rd->next)
num_riders++;
for (angle = 0, rd = mv->riders; rd; rd = rd->next) {
aw_mover_rider_change (aw_int (AW_ENTITY_ID), rd->session, 100, angle, -35, 1800, 0);
angle += 3600 / num_riders;
log_msg ("Rider %d changed to riding position", rd->session);
}
}
void move_to (struct mover* mv)
{
int interval;
int wp;
int dx;
int dy;
int dz;
int dyaw;
int dpitch;
int droll;
interval = aw_int (AW_WORLD_AVATAR_REFRESH_RATE) * WAYPOINT_RESOLUTION;
wp = (int)mv->last_waypoint;
if (wp < 0 || wp >= MAX_WAYPOINTS)
wp = 0;
if (wp) {
dx = mv->cur_x + ((waypoints[wp].x - waypoints[wp - 1].x) / interval);
dy = mv->cur_y + ((waypoints[wp].y - waypoints[wp - 1].y) / interval);
dz = mv->cur_z + ((waypoints[wp].z - waypoints[wp - 1].z) / interval);
dyaw = mv->cur_yaw + ((waypoints[wp].yaw - waypoints[wp - 1].yaw) / interval);
dpitch = mv->cur_pitch + ((waypoints[wp].pitch - waypoints[wp - 1].pitch) / interval);
droll = mv->cur_roll + ((waypoints[wp].roll - waypoints[wp - 1].roll) / interval);
} else {
dx = waypoints[wp].x;
dy = waypoints[wp].y;
dz = waypoints[wp].z;
dyaw = waypoints[wp].yaw;
dpitch = waypoints[wp].pitch;
droll = waypoints[wp].roll;
}
mv->cur_x = dx;
mv->cur_y = dy;
mv->cur_z = dz;
mv->cur_yaw = dyaw;
mv->cur_pitch = dpitch;
mv->cur_roll = droll;
aw_mover_set_position (mv->id,
mv->init_x + dx, mv->init_y + dy, mv->init_z + dz,
mv->init_yaw + dyaw, mv->init_pitch + dpitch, mv->init_roll + droll);
mv->last_move = aw_tick ();
log_msg ("Mover MOV: %u delta: %d %d %d %d %d %d", mv->id,
dx, dy, dz, dyaw, dpitch, droll);
}
void idle (struct mover* mv)
{
if (mv->last_move + IDLE_INTERVAL < aw_tick ())
move_to (mv);
}
int update ()
{
struct mover* mv;
struct rider* rd;
int count;
unsigned int frame_time;
for (count = 0, mv = movers; mv; mv = mv->next) {
if (!mv->start
|| mv->last_update + UPDATE_INTERVAL > aw_tick ())
continue;
frame_time = aw_tick () - mv->start;
// log_msg ("UPDATE : %u state %d dt %d", mv->id, mv->state, frame_time);
if (mv->state != AW_MOVER_STATE_PAUSE
&& mv->state != AW_MOVER_STATE_MOVE) {
if (mv->state != AW_MOVER_STATE_IDLE)
continue;
// Set state to Pause, so other users can join in
pause (mv->id);
mv->state = STATE_INVALIDATE; // invalidate state
// Move to first waypoint
mv->last_waypoint = 0.0f;
move_to (mv);
// Add bot as driver to first mover
if (mv == movers)
aw_mover_rider_add (mv->id, aw_session (), 450, 1800, -30, 1800, 0);
mv->start = aw_tick ();
announce (READY_MSG);
continue;
}
idle (mv); // see if we have to send pos to prevent timeout
if (frame_time < 15000) {
// wait to get ready or something else?
continue;
} else if (mv->last_waypoint >= 0.0f) {
if (mv->state != AW_MOVER_STATE_MOVE
&& mv->state != STATE_INVALIDATE) {
if (mv->rider_count <= 0) {
mv->start = aw_tick ();
announce (POSTPONE_MSG);
continue;
}
start (mv->id);
mv->state = STATE_INVALIDATE; // invalidate state
announce (START_MSG);
} else if (mv->state == AW_MOVER_STATE_MOVE) {
mv->last_waypoint += 1.0f / (float)(aw_int (AW_WORLD_AVATAR_REFRESH_RATE) * WAYPOINT_RESOLUTION);
if (mv->last_waypoint >= MAX_WAYPOINTS)
mv->last_waypoint = 0.0f;
move_to (mv);
count++;
if (mv->last_waypoint == 0.0f)
mv->last_waypoint = -1.0f;
}
} else if (mv->last_waypoint < 0.0f && mv->state == AW_MOVER_STATE_MOVE) {
// Don't pause before mover has reached the last waypoint,
if (mv->last_update + (2000 / aw_int (AW_WORLD_AVATAR_REFRESH_RATE)) < aw_tick ()) {
pause (mv->id);
mv->state = STATE_INVALIDATE; // invalidate state
announce (STOP_MSG);
} else
continue;
} else if (mv->last_waypoint < 0.0f && mv->state == AW_MOVER_STATE_PAUSE) {
for (rd = mv->riders; rd; rd = rd->next)
if (rd->session != aw_session ())
aw_mover_rider_delete (mv->id, rd->session);
mv->start = aw_tick ();
mv->last_waypoint = 0.0f;
announce (READY_MSG);
count++;
}
mv->last_update = aw_tick ();
}
return count;
}
main (int argc, char *argv[])
{
int rc;
int owner;
char* pw;
char* world;
// check command line
if (argc < 4) {
log_msg ("Usage: %s citnumber password world", argv[0]);
exit (1);
}
// initialize Active Worlds API
if (rc = aw_init (AW_BUILD)) {
log_msg ("Unable to initialize API (reason %d)", rc);
exit (1);
}
// install handler for callbacks and events
aw_callback_set (AW_CALLBACK_ENTER, handle_enter);
aw_event_set (AW_EVENT_WORLD_DISCONNECT, handle_world_disconnect);
aw_event_set (AW_EVENT_ENTITY_ADD, handle_entity_add);
aw_event_set (AW_EVENT_ENTITY_CHANGE, handle_entity_change);
aw_event_set (AW_EVENT_ENTITY_DELETE, handle_entity_delete);
aw_event_set (AW_EVENT_ENTITY_RIDER_ADD, handle_entity_rider_add);
aw_event_set (AW_EVENT_ENTITY_RIDER_DELETE, handle_entity_rider_delete);
aw_event_set (AW_EVENT_ENTITY_RIDER_CHANGE, handle_entity_rider_change);
// create bot instance
if (rc = aw_create ("", 0, 0)) {
log_msg ("Unable to create bot instance (reason %d)", rc);
exit (1);
}
owner = atoi (argv[1]);
pw = argv[2];
world = argv[3];
// log bot into the universe
aw_int_set (AW_LOGIN_OWNER, owner);
aw_string_set (AW_LOGIN_PRIVILEGE_PASSWORD, pw);
aw_string_set (AW_LOGIN_APPLICATION, "MoverControlSample");
aw_string_set (AW_LOGIN_NAME, "MoverControl");
if (rc = aw_login ()) {
log_msg ("Unable to login (reason %d)", rc);
exit (1);
}
log_msg ("Successfully logged into Universe");
strcpy (mv_model, "trolly.rwx");
mv_owner = 101;
// log bot into the world
if (rc = aw_enter (world)) {
log_msg ("Unable to enter world (reason %d)", rc);
exit (1);
}
log_msg ("Successfully entered World %s", world);
// main event loop
while (!aw_wait (WAIT_TIME))
update ();
// close everything
aw_destroy ();
aw_term ();
return 0;
}
/*-----------------------------------------------------------------------------
E O F
-----------------------------------------------------------------------------*/