MuckyFoot-UrbanChaos/thrust/player.cpp
2017-05-20 11:14:17 +10:00

922 lines
19 KiB
C++

//
// A player.
//
#include "always.h"
#include "key.h"
#include "font.h"
#include "game.h"
#include "gamestate.h"
#include "log.h"
#include "net.h"
#include "os.h"
#include "player.h"
#include "server.h"
#include "ship.h"
//
// The keypresses of all the players.
//
PLAYER_Key PLAYER_key[PLAYER_MAX_PLAYERS];
SLONG PLAYER_key_start;
SLONG PLAYER_key_gameturn;
//
// The messages relating to players joining and leaving the game.
//
PLAYER_Message *PLAYER_message[PLAYER_NUM_MESSAGES];
SLONG PLAYER_message_start;
SLONG PLAYER_message_gameturn;
//
// The gamestate into the past.
//
PLAYER_Gamestate PLAYER_gamestate[PLAYER_NUM_GAMESTATES];
SLONG PLAYER_gamestate_start;
SLONG PLAYER_gamestate_gameturn;
//
// Our local SHIP_ship \ PLAYER_key index.
//
SLONG PLAYER_local_index;
void PLAYER_init()
{
memset(PLAYER_key, 0, sizeof(PLAYER_key ));
memset(PLAYER_message, 0, sizeof(PLAYER_message ));
memset(PLAYER_gamestate, 0, sizeof(PLAYER_gamestate));
PLAYER_key_start = -1;
PLAYER_key_gameturn = -1;
PLAYER_message_start = -1;
PLAYER_message_gameturn = -1;
PLAYER_gamestate_start = -1;
PLAYER_gamestate_gameturn = -1;
}
void PLAYER_new_gameturn(SLONG gameturn)
{
if (PLAYER_gamestate_start == -1)
{
PLAYER_gamestate_start = gameturn;
PLAYER_gamestate_gameturn = gameturn;
}
if (WITHIN(gameturn, PLAYER_gamestate_start, PLAYER_gamestate_gameturn))
{
//
// Overwrite an existing gamestate.
//
}
else
{
//
// We should never have to remember so long in the past!
//
ASSERT(gameturn >= PLAYER_gamestate_start);
PLAYER_gamestate_gameturn = gameturn;
if (PLAYER_gamestate_start < PLAYER_gamestate_gameturn - (PLAYER_NUM_GAMESTATES - 1))
{
PLAYER_gamestate_start = PLAYER_gamestate_gameturn - (PLAYER_NUM_GAMESTATES - 1);
}
}
//
// Store gamestate.
//
GAMESTATE_store(&PLAYER_gamestate[gameturn & (PLAYER_NUM_GAMESTATES - 1)].gs);
PLAYER_gamestate[gameturn & (PLAYER_NUM_GAMESTATES - 1)].gameturn = gameturn;
}
void PLAYER_restore_gamestate(SLONG gameturn)
{
ASSERT(WITHIN(gameturn, PLAYER_gamestate_start, PLAYER_gamestate_gameturn));
ASSERT(PLAYER_gamestate[gameturn & (PLAYER_NUM_GAMESTATES - 1)].gameturn == gameturn);
GAMESTATE_restore(&PLAYER_gamestate[gameturn & (PLAYER_NUM_GAMESTATES - 1)].gs);
}
SLONG PLAYER_create_local(
CBYTE *name,
UBYTE red,
UBYTE green,
UBYTE blue,
float ship_mass,
float ship_power)
{
SLONG num_bytes;
void *data;
//
// Say what we're doing...
//
OS_clear_screen();
OS_scene_begin();
FONT_draw(0.5F, 0.4F, 0xffffff, 1.0F, -1, "Awaiting gamestate...");
OS_scene_end();
OS_show();
//
// Send a message to the server requesting to join the game.
//
LOG_message(0xffffff, "Requesting to joing game");
struct
{
SLONG gameturn;
SERVER_Block_request_join join;
} join_message;
join_message.gameturn = GAME_turn;
join_message.join.type = SERVER_BLOCK_TYPE_REQUEST_JOIN;
join_message.join.red = red;
join_message.join.green = green;
join_message.join.blue = blue;
join_message.join.mass = ship_mass;
join_message.join.power = ship_power;
//
// Send this message guaranteed.
//
NET_player_message_send(sizeof(join_message), &join_message, TRUE);
//
// Wait until the server responds with a copy of gamestate.
//
SLONG start = OS_ticks();
while(1)
{
//
// Keep our gameturns up-to-date.
//
while(GAME_tick < OS_ticks())
{
GAME_process += 1;
GAME_tick += GAME_TICKS_PER_PROCESS;
GAME_turn = GAME_process >> 4;
}
//
// Poll for the gamestate received message.
//
switch(NET_player_message_receive(&num_bytes, &data))
{
case NET_PLAYER_MESSAGE_NONE:
if (OS_ticks() > start + 4 * 1000)
{
//
// More than 4 seconds!
//
return PLAYER_CREATE_TIMED_OUT;
}
break;
case NET_PLAYER_MESSAGE_LOST_CONNECTION:
return PLAYER_CREATE_LOST_CONNECTION;
case NET_PLAYER_MESSAGE_FROM_SERVER:
//
// We should only ever get a GAMESTATE message.
//
{
SLONG exit_loop = FALSE;
UBYTE *upto = ((UBYTE *) data) + 4;
SLONG gameturn = ((SLONG *) data)[0];
while(!exit_loop)
{
if (upto - ((UBYTE *) data) >= num_bytes)
{
//
// Finished processing the message.
//
break;
}
else
{
switch(*upto)
{
case SERVER_BLOCK_TYPE_GAMESTATE:
{
SERVER_Block_gamestate *sbg = (SERVER_Block_gamestate *) upto;
LOG_message(0xffffff, "Got gamestate");
//
// Initialise all the state.
//
memset(PLAYER_key, 0, sizeof(PLAYER_key ));
memset(PLAYER_message, 0, sizeof(PLAYER_message ));
memset(PLAYER_gamestate, 0, sizeof(PLAYER_gamestate));
PLAYER_key_start = -1;
PLAYER_key_gameturn = -1;
PLAYER_message_start = -1;
PLAYER_message_gameturn = -1;
PLAYER_gamestate_start = -1;
PLAYER_gamestate_gameturn = -1;
//
// We know one of the gamestates...
//
PLAYER_gamestate_start = sbg->gameturn;
PLAYER_gamestate_gameturn = sbg->gameturn;
PLAYER_gamestate[sbg->gameturn & (PLAYER_NUM_GAMESTATES - 1)].gameturn = sbg->gameturn;
PLAYER_gamestate[sbg->gameturn & (PLAYER_NUM_GAMESTATES - 1)].gs = sbg->gs;
//
// We don't know about any keypresses or messages.
//
PLAYER_key_start = sbg->gameturn;
PLAYER_key_gameturn = sbg->gameturn;
PLAYER_message_start = sbg->gameturn;
PLAYER_message_gameturn = sbg->gameturn;
//
// Restore gamestate.
//
GAMESTATE_restore(&sbg->gs);
//
// This is the gameturn we are upto...
//
{
SLONG dprocess = (sbg->gameturn << 4) - GAME_process;
GAME_turn = sbg->gameturn;
GAME_process = sbg->gameturn << 4;
GAME_tick += dprocess * GAME_TICKS_PER_PROCESS;
}
//
// This is the order of events...
//
// GAME_turn += 1;
// SHIP_set_active()
// Get keypresses.
// Process game...
//
// So subtract one from the process. Get it?
//
GAME_process -= 1;
GAME_turn -= 1;
GAME_tick -= GAME_TICKS_PER_PROCESS;
PLAYER_key_start -= 1;
PLAYER_key_gameturn -= 1;
PLAYER_message_start -= 1;
PLAYER_message_gameturn -= 1;
goto got_gamestate;
}
break;
case SERVER_BLOCK_TYPE_PING:
//
// We can ignore these now...
//
upto += sizeof(SERVER_Block_ping);
break;
default:
ASSERT(0);
break;
}
}
}
}
break;
default:
ASSERT(0);
break;
}
}
got_gamestate:;
//
// Tell the server that we've got gamestate. Send the data guaranteed.
//
LOG_message(0xffffff, "Sending acknoledgement that I received gamestate.");
struct
{
SLONG gameturn;
SERVER_Block_received_gamestate received;
} got_it;
got_it.gameturn = GAME_turn;
got_it.received.type = SERVER_BLOCK_TYPE_RECEIVED_GAMESTATE;
NET_player_message_send(sizeof(got_it), &got_it, TRUE);
return PLAYER_CREATE_OK;
}
SLONG PLAYER_process(SLONG *rollback, SLONG ignore_server_messages)
{
SLONG i;
SLONG j;
SLONG num_bytes;
SLONG rollback_happened;
void *data;
PLAYER_Key *pk;
SHIP_Ship *ss;
UBYTE local;
//
// Rollback only happens we get a different keypress before PLAYER_key_gameturn,
// not GAME_turn. They should only ever be one different!
//
ASSERT(PLAYER_key_gameturn == GAME_turn - 1);
*rollback = PLAYER_key_gameturn;
rollback_happened = FALSE;
//
// Clear out the new keypresses and update the start of the keypress history.
//
while (PLAYER_key_gameturn < GAME_turn)
{
PLAYER_key_gameturn += 1;
for (i = 0; i < PLAYER_MAX_PLAYERS; i++)
{
PLAYER_key[i].key[PLAYER_key_gameturn & (PLAYER_NUM_KEYS - 1)] = 0;
}
if (PLAYER_key_start <= PLAYER_key_gameturn - PLAYER_NUM_KEYS)
{
//
// The circular queue is going to overwrite an old keypress.
//
PLAYER_key_start = PLAYER_key_gameturn - (PLAYER_NUM_KEYS - 1);
}
}
//
// Update the keypress history.
//
local = 0;
if (KEY_on[KEY_Z]) {local |= PLAYER_KEY_LEFT; }
if (KEY_on[KEY_X]) {local |= PLAYER_KEY_RIGHT;}
if (KEY_on[KEY_V]) {local |= PLAYER_KEY_THRUST;}
if (KEY_on[KEY_G])
{
KEY_on[KEY_G] = 0;
local |= PLAYER_KEY_TRACTOR_BEAM;
}
for (i = 0; i < PLAYER_MAX_PLAYERS; i++)
{
pk = &PLAYER_key[i];
ss = &SHIP_ship [i];
if (ss->flag & SHIP_FLAG_USED)
{
if (ss->flag & SHIP_FLAG_LOCAL)
{
//
// This is a local player.
//
if (ss->flag & SHIP_FLAG_ACTIVE)
{
pk->key[GAME_turn & (PLAYER_NUM_KEYS - 1)] = local;
}
else
{
//
// A non-active player can't press his keys.
//
pk->key[GAME_turn & (PLAYER_NUM_KEYS - 1)] = 0;
}
}
else
{
if (ss->flag & SHIP_FLAG_ACTIVE)
{
UBYTE last;
if (ss->active == GAME_turn)
{
//
// The keypresses start off blank.
//
last = 0;
}
else
{
//
// Guess the keys that the remote player has pressed down.
// The last gameturns keypresses are a good starting point!
//
last = pk->key[(GAME_turn - 1) & (PLAYER_NUM_KEYS - 1)];
//
// Assume remote player's key stay the same- except for tractor
// beam, which we assume stays on for only one gameturn.
//
last &= ~PLAYER_KEY_TRACTOR_BEAM;
}
pk->key[GAME_turn & (PLAYER_NUM_KEYS - 1)] = last;
}
else
{
//
// A non-active player can't press his keys.
//
pk->key[GAME_turn & (PLAYER_NUM_KEYS - 1)] = 0;
}
}
}
}
//
// Send the server our keypresses.
//
ASSERT(WITHIN(PLAYER_local_index, 0, SHIP_MAX_SHIPS - 1));
if (SHIP_ship[PLAYER_local_index].flag & SHIP_FLAG_ACTIVE)
{
SLONG num_turns;
num_turns = GAME_turn - SHIP_ship[PLAYER_local_index].active + 1;
if (num_turns > 32)
{
num_turns = 32;
}
if (num_turns > 0)
{
PLAYER_Key *pk = &PLAYER_key[PLAYER_local_index];
struct
{
#define PLAYER_MAX_NUM_KEYS 256
SLONG gameturn;
union
{
SERVER_Block_my_keypress_list sbm;
UBYTE room[sizeof(SERVER_Block_my_keypress_list) + 2 * PLAYER_MAX_NUM_KEYS];
};
} my_keys;
my_keys.gameturn = GAME_turn;
my_keys.sbm.type = SERVER_BLOCK_TYPE_MY_KEYPRESS_LIST;
my_keys.sbm.num_keys = num_turns;
my_keys.sbm.ship_index = PLAYER_local_index;
my_keys.sbm.hash = SHIP_ship[PLAYER_local_index].x * SHIP_ship[PLAYER_local_index].y + SHIP_ship[PLAYER_local_index].angle;
for (i = 0; i < num_turns; i++)
{
my_keys.sbm.key[i] = pk->key[(GAME_turn - i) & (PLAYER_NUM_KEYS - 1)];
}
NET_player_message_send(sizeof(SLONG) + sizeof(SERVER_Block_my_keypress_list) + my_keys.sbm.num_keys, &my_keys);
}
}
//
// Free up the old wrapped-around messages.
//
while(PLAYER_message_gameturn < GAME_turn)
{
PLAYER_message_gameturn += 1;
if (PLAYER_message_start <= PLAYER_message_gameturn - PLAYER_NUM_MESSAGES)
{
//
// The circular queue is going to overwrite an old message.
//
PLAYER_message_start = PLAYER_message_gameturn - (PLAYER_NUM_MESSAGES - 1);
}
//
// free() up the old messages from the last time this
// gameturn was used.
//
while(PLAYER_message[PLAYER_message_gameturn & (PLAYER_NUM_MESSAGES - 1)])
{
void *free_me_up;
free_me_up = PLAYER_message[PLAYER_message_gameturn & (PLAYER_NUM_MESSAGES - 1)];
PLAYER_message[PLAYER_message_gameturn & (PLAYER_NUM_MESSAGES - 1)] = PLAYER_message[PLAYER_message_gameturn & (PLAYER_NUM_MESSAGES - 1)]->next;
free(free_me_up);
}
}
if (!ignore_server_messages)
{
//
// Get the server messages.
//
while(1)
{
//
// Now process network messages. We might have to change the
// keypresses of the remote players.
//
switch(NET_player_message_receive(&num_bytes, &data))
{
case NET_PLAYER_MESSAGE_NONE:
//
// No more messages.
//
goto no_more_messages;
case NET_PLAYER_MESSAGE_LOST_CONNECTION:
//
// Arggh! We can't find the server anymore.
//
*rollback = 0;
rollback_happened = FALSE;
return FALSE;
case NET_PLAYER_MESSAGE_FROM_SERVER:
{
SLONG exit_loop = FALSE;
UBYTE *upto = ((UBYTE *) data) + 4;
SLONG gameturn = ((SLONG *) data)[0];
while(!exit_loop)
{
if (upto - ((UBYTE *) data) >= num_bytes)
{
//
// Finished processing the message.
//
break;
}
else
{
switch(*upto)
{
case SERVER_BLOCK_TYPE_REQUEST_JOIN:
ASSERT(0);
break;
case SERVER_BLOCK_TYPE_NEW_PLAYER:
{
SERVER_Block_new_player *sbnp = (SERVER_Block_new_player *) upto;
LOG_message(0xffffff, "Got new player %d packet", sbnp->ship_index);
//
// Create a new player message structure and put this message in
// the player message queue.
//
PLAYER_Message *pm = (PLAYER_Message *) malloc(sizeof(PLAYER_Message));
pm->new_player = *sbnp;
pm->next = PLAYER_message[GAME_turn & (PLAYER_NUM_MESSAGES - 1)];
PLAYER_message[GAME_turn & (PLAYER_NUM_MESSAGES - 1)] = pm;
LOG_message(0xffff00, "New player active %d", sbnp->active);
upto += sizeof(SERVER_Block_new_player);
}
break;
case SERVER_BLOCK_TYPE_MY_KEYPRESS_LIST:
ASSERT(0);
break;
case SERVER_BLOCK_TYPE_REMOTE_KEYPRESS_LIST:
{
SLONG i;
SLONG j;
SLONG turn;
UBYTE key;
UBYTE repeat;
SHIP_Ship *ss;
PLAYER_Key *pk;
SERVER_Block_remote_keypress_list *sbk = (SERVER_Block_remote_keypress_list *) upto;
ASSERT(WITHIN(sbk->ship_index, 0, SHIP_MAX_SHIPS - 1));
ss = &SHIP_ship[sbk->ship_index];
if (!(ss->flag & SHIP_FLAG_USED))
{
//
// This is a keypress list for a ship that we haven't
// created yet!
//
}
else
{
//
// Overwrite the keypresses we guessed with the keypresses that
// actaully hapenned. If there is a discrepency, then we must
// roll back to that gameturn and reprocess everything.
//
pk = &PLAYER_key[sbk->ship_index];
for (i = 0; i < sbk->num_keys; i++)
{
turn = sbk->gameturn - i;
if (WITHIN(turn, PLAYER_key_start, PLAYER_key_gameturn))
{
if (pk->key[turn & (PLAYER_NUM_KEYS - 1)] != sbk->key[i])
{
if (turn <= *rollback)
{
//
// We guessed wrong! We must rollback.
//
*rollback = turn;
rollback_happened = TRUE;
ASSERT(*rollback < GAME_turn);
if (i == 0)
{
//
// Update the guessing...
//
for (j = sbk->gameturn + 1; j <= GAME_turn; j++)
{
//
// Assume the keys we don't know are the same as the
// last key press we got. Except the tractor beam.
//
pk->key[j & (PLAYER_NUM_KEYS - 1)] = sbk->key[0] & ~PLAYER_KEY_TRACTOR_BEAM;
}
}
}
}
pk->key[turn & (PLAYER_NUM_KEYS - 1)] = sbk->key[i];
}
}
}
upto += sizeof(SERVER_Block_remote_keypress_list);
upto += sizeof(UBYTE) * sbk->num_keys;
}
break;
case SERVER_BLOCK_TYPE_PING:
ASSERT(0);
break;
default:
ASSERT(0);
break;
}
}
}
}
break;
default:
ASSERT(0);
break;
}
}
no_more_messages:;
}
if (!rollback_happened)
{
//
// 0 means no rollback.
//
*rollback = 0;
}
else
{
ASSERT(*rollback < GAME_turn);
//
// Rollback gamestate.
//
PLAYER_restore_gamestate(*rollback);
}
return TRUE;
}
void PLAYER_process_messages(SLONG gameturn)
{
ASSERT(WITHIN(gameturn, PLAYER_message_start, PLAYER_message_gameturn));
PLAYER_Message *pm = PLAYER_message[gameturn & (PLAYER_NUM_MESSAGES - 1)];
while(pm)
{
switch(pm->type)
{
case SERVER_BLOCK_TYPE_NEW_PLAYER:
{
SHIP_Ship *ss;
ASSERT(WITHIN(pm->new_player.ship_index, 0, SHIP_MAX_SHIPS - 1));
ss = &SHIP_ship[pm->new_player.ship_index];
memset(ss, 0, sizeof(SHIP_Ship));
ss->flag = SHIP_FLAG_USED;
ss->active = pm->new_player.active;
ss->red = pm->new_player.red;
ss->green = pm->new_player.green;
ss->blue = pm->new_player.blue;
ss->mass = pm->new_player.mass;
ss->power = pm->new_player.power;
ss->x = pm->new_player.x;
ss->y = pm->new_player.y;
ss->angle = 0.0F;
if (pm->new_player.local)
{
//
// This is the local player.
//
ss->flag |= SHIP_FLAG_LOCAL;
PLAYER_local_index = pm->new_player.ship_index;
}
//
// Up until the gameturn before this ship goes active, all it's
// keypresses are zero.
//
memset(&PLAYER_key[pm->new_player.ship_index], 0, sizeof(PLAYER_Key));
PLAYER_key[pm->new_player.ship_index].known = ss->active - 1;
}
break;
case SERVER_BLOCK_TYPE_PLAYER_LEFT:
break;
default:
ASSERT(0);
break;
}
pm = pm->next;
}
}
void PLAYER_press_keys(SLONG gameturn)
{
SLONG i;
PLAYER_Key *pk;
SHIP_Ship *ss;
for (i = 0; i < PLAYER_MAX_PLAYERS; i++)
{
ss = &SHIP_ship[i];
if (ss->flag & SHIP_FLAG_USED)
{
ss->flag &= ~SHIP_FLAG_KEY_LEFT;
ss->flag &= ~SHIP_FLAG_KEY_RIGHT;
ss->flag &= ~SHIP_FLAG_KEY_THRUST;
ss->flag &= ~SHIP_FLAG_KEY_TRACTOR_BEAM;
if (gameturn < ss->active)
{
//
// No keys presses before is goes active.
//
}
else
{
pk = &PLAYER_key[i];
if (pk->key[gameturn & (PLAYER_NUM_KEYS - 1)] & PLAYER_KEY_LEFT ) {ss->flag |= SHIP_FLAG_KEY_LEFT ;}
if (pk->key[gameturn & (PLAYER_NUM_KEYS - 1)] & PLAYER_KEY_RIGHT ) {ss->flag |= SHIP_FLAG_KEY_RIGHT ;}
if (pk->key[gameturn & (PLAYER_NUM_KEYS - 1)] & PLAYER_KEY_THRUST ) {ss->flag |= SHIP_FLAG_KEY_THRUST ;}
if (pk->key[gameturn & (PLAYER_NUM_KEYS - 1)] & PLAYER_KEY_TRACTOR_BEAM) {ss->flag |= SHIP_FLAG_KEY_TRACTOR_BEAM;}
}
}
}
}