Black-Cell.net

  • December 17, 2018, 11:45:14 AM
  • Welcome, Guest
Please login or register.

Login with username, password and session length
Advanced search  

News:

Pages: [1]   Go Down

Author Topic: Getting a random player revisited ~ Dirty crash on next map load  (Read 1745 times)

Reborn

  • Donator
  • Offline Offline
  • Posts: 200
    • View Profile
Getting a random player revisited ~ Dirty crash on next map load
« on: December 13, 2007, 06:36:37 AM »

I kinda stopped working on Server Side Hunt The Player for a while, I got motivated for like a day, then stopped. I have a confusing problem that is driving me round the bend.

I have a function that is called to select a player at random (i didn't write this function, it was kindly given to me by WhiteDragon):

Code: [Select]
GameObject *Get_Random_Player(int Team) {
std::vector<cPlayer*> Players;
for (GenericSLNode* PlayerIter = PlayerList->HeadNode; (PlayerIter != NULL); PlayerIter = PlayerIter->NodeNext) {
cPlayer *p = (cPlayer *)PlayerIter->NodeData;
if (p->IsActive && (p->PlayerType.Get() == Team || Team == 2)) {
Players.push_back(p);
}
}
if (Players.empty()) {
return 0;
}

int Rand = Commands->Get_Random_Int(0,Players.size());
return (GameObject*)Players[Rand]->Owner.Reference->obj;
}

Code: [Select]
GameObject *Get_Random_Player(int Team);

I changed void Level_Loaded() so I could sneak in some code to choose a random player when the map loads, here it is:

Code: [Select]
void Level_Loaded() {
GameObject *htpcontroller = Commands->Create_Object("Invisible_Object",Vector3(0.0f,0.0f,0.0f));
Commands->Attach_Script(htpcontroller,"SSHTP_Messages","");
Commands->Attach_Script(htpcontroller,"wait_till_player_joins","");

strncpy(Data->CurrMap,The_Game()->MapName,29);

// etc etc etc

The SSHTP_Messages are just console input messages explaining the game mode.

Here is wait_till_player_joins:

Code: [Select]
void wait_till_player_joins::Created(GameObject *obj){
int count = Get_Player_Count();

if (count == 0){
Commands->Start_Timer(obj,this,2.0f,1);
}
// If the player count is 1 or more, then it activates the virus chooser script
else if (count >=1){
Commands->Attach_Script(obj,"virus_chooser","");
}
}
// this is just the code that waits two seconds then makes it look again for players
void wait_till_player_joins::Timer_Expired(GameObject *obj, int number) {
if (number == 1){
Commands->Attach_Script(obj,"wait_till_player_joins","");
}
}
Code: [Select]
class wait_till_player_joins : public ScriptImpClass {
void Created(GameObject *obj);
void Timer_Expired(GameObject *obj, int number);
};

It's just a cheap timer to wait until someone joins the server. There are more then likely allot better ways of doing it, but it was the first that came to mind. I am open to suggests on a better way. But for not that isn't the problem.
As you can see, if someone joins the servers then "virus_chooser" is called. This is the code to choose a victim of the mode, here it is:

Code: [Select]
void virus_chooser::Created(GameObject *obj){

// get the player count of the server
int count = Get_Player_Count();
// if the player count is 0, then start a two second timer and look again in two seconds
if (count == 0){
Commands->Start_Timer(obj,this,2.0f,1);
}
else {
GameObject *victim = Get_Random_Player(2);
const char *Nick = Get_Player_Name(victim);
// Announce to the server that the pathogen has random infected the player
Console_Input(StrFormat("msg The pathogen has randomly infected: %s",Nick).c_str());
// attach the hunted player script to that player
Commands->Attach_Script(victim,"hunted_player","");
delete[] Nick;
}

}
// code to wait two seconds if the player count is 0
void virus_chooser::Timer_Expired(GameObject *obj, int number) {
if (number == 1){
Commands->Attach_Script(obj,"virus_chooser","");
}
}


Code: [Select]
class virus_chooser : public ScriptImpClass {
void Created(GameObject *obj);
void Timer_Expired(GameObject *obj, int number);
};
As you can see, it calls the function I copied at the top of the thread.
This all works fine. I start the server, join it a few seconds later and the server chooses me as the victim.
Everything works as it should do.

That is until the time expires and the next map loads. When the next map loads the server crashes. These are the lines the debugger call stack points to:


Quote
Commands->Attach_Script(obj,"virus_chooser","");

from the script:
Code: [Select]
void wait_till_player_joins::Created(GameObject *obj){
int count = Get_Player_Count();

if (count == 0){
Commands->Start_Timer(obj,this,2.0f,1);
}
// If the player count is 1 or more, then it activates the virus chooser script
else if (count >=1){
Commands->Attach_Script(obj,"virus_chooser","");
}
}
// this is just the code that waits two seconds then makes it look again for players
void wait_till_player_joins::Timer_Expired(GameObject *obj, int number) {
if (number == 1){
Commands->Attach_Script(obj,"wait_till_player_joins","");
}
}


Quote
GameObject *victim = Get_Random_Player(2);

from the script:
Code: [Select]
void virus_chooser::Created(GameObject *obj){

// get the player count of the server
int count = Get_Player_Count();
// if the player count is 0, then start a two second timer and look again in two seconds
if (count == 0){
Commands->Start_Timer(obj,this,2.0f,1);
}
else {
GameObject *victim = Get_Random_Player(2);
const char *Nick = Get_Player_Name(victim);
// Announce to the server that the pathogen has random infected the player
Console_Input(StrFormat("msg The pathogen has randomly infected: %s",Nick).c_str());
// attach the hunted player script to that player
Commands->Attach_Script(victim,"hunted_player","");
delete[] Nick;
}

}

And the first one in the call stack is this:

Quote
   return (GameObject*)Players[Rand]->Owner.Reference->obj;

from:

Code: [Select]
GameObject *Get_Random_Player(int Team) {
std::vector<cPlayer*> Players;
for (GenericSLNode* PlayerIter = PlayerList->HeadNode; (PlayerIter != NULL); PlayerIter = PlayerIter->NodeNext) {
cPlayer *p = (cPlayer *)PlayerIter->NodeData;
if (p->IsActive && (p->PlayerType.Get() == Team || Team == 2)) {
Players.push_back(p);
}
}
if (Players.empty()) {
return 0;
}

int Rand = Commands->Get_Random_Int(0,Players.size());
return (GameObject*)Players[Rand]->Owner.Reference->obj;
}


I forgot to mention that everyone is teamed to the mutant side (team 2), this is done on the created event here:

Code: [Select]
void M00_GrantPowerup_Created::Created(GameObject *obj) {
// This code changes everyone to team nuetral by checking on created that they are team 2, and if they are not, then change them
if (Commands->Get_Player_Type(obj) == 2){
// nothing yet
}
else {
Change_Team(obj, 2);
}
// Grant them infinate ammo from the start
Enable_Infinite_Ammo();
//etc etc

Could it be that because you are allready team 2 when the next map loads the Get_Random_Player script causes it to crash?

In anycase, if you can help, then please do, I would appreciate it allot. I will send you the source if you need to look at it. I didn't post it here as I would prefer to keep it private until such time that the game mode is stable and complete, then it can be released.
Logged

Reborn

  • Donator
  • Offline Offline
  • Posts: 200
    • View Profile
Re: Getting a random player revisited ~ Dirty crash on next map load
« Reply #1 on: December 13, 2007, 03:54:49 PM »

Case solved and fixed.

It was crashing because I was calling "Get_Random_Player" on map load, this was trying to get a random player that was still loading the game, so therefore he didn't have a GameObject * yet.

I figured there playerID must still be valid whilst loading, even if they have no GameObject *, so Roshambo was kind enough on request to modify WhiteDragon's function to return a random players ID instead of there GameObject *.

After he did that I solved the crash by doing this:

Code: [Select]
void virus_chooser::Created(GameObject *obj){

// get the player count of the server
int count = Get_Player_Count();
// if the player count is 0, then start a two second timer and look again in two seconds
if (count == 0){
Commands->Start_Timer(obj,this,2.0f,1);
}
else {
int ID = Get_Random_Player_ID(2);
GameObject *victim = Get_GameObj(ID);
if(victim){
const char *Nick = Get_Player_Name(victim);
// Announce to the server that the pathogen has random infected the player
Console_Input(StrFormat("msg The pathogen has randomly infected: %s",Nick).c_str());
// attach the hunted player script to that player
Commands->Attach_Script(victim,"hunted_player","");
delete[] Nick;
}
else{
Commands->Start_Timer(obj,this,2.0f,1);
}
}
}

The other option was going to be iterate the player list and make sure all players are loaded after the map load event before choosing a random player. But I think this way is better because at least this way I don't have to keep looping the player list iteration until all players are loaded before I choose a random player. That would mean more work on the server, plus the victim wouldn't be choosen until the last player has loaded everytime.
Logged

vloktboky

  • Offline Offline
  • Posts: 2631
    • View Profile
Re: Getting a random player revisited ~ Dirty crash on next map load
« Reply #2 on: December 13, 2007, 07:36:52 PM »

You may want to re-explore that Get_Random_Player function. There is no need to be using a vector object the way you are there. You're attempting to pour a glass of tea by dumping pitcher A into pitcher B, then to your glass.

I told you over AIM, use an integer and iterate through the list itself.

Code: [Select]
GameObject* Get_Random_Player(int Team)
{
// Calculate team count, return error if no-one is on the team
int nTeamCount = Get_Team_Player_Count(Team);
if (NULL == PlayerList || nTeamCount <= 0) return NULL;

// Calculate random iteration step
int nCount = 0, nRand = Commands->Get_Random_Int(0,nTeamCount);
GenericSLNode *pIter = PlayerList->HeadNode;
while (NULL != pIter)
{
// Extract player from data, validate
cPlayer *pPlayer = (cPlayer*)pIter->NodeData;
bool bValid = (NULL != pPlayer && true == pPlayer->IsActive &&
(pPlayer->PlayerType.Get() == Team || 2 == Team));

// Advance step. Is this the one?
if (nCount < nRand || false == bValid)
{
// Increment the count if this player was valid
// This will make sure we get the Nth player on X team
if (true == bValid)
{
nCount++;
}

pIter = pIter->NodeNext;
continue;
}

// We got him
return (NULL != pPlayer->Owner.Reference ?
(GameObject*)pPlayer->Owner.Reference->obj :
NULL);
}

// Could not find a player
return NULL;
}

A few pointers weren't being checked for validity in the aforementioned code. This may have led to your crash bug and explain why returning the ID (which isn't necessarily a bad design choice) of the player "solved" the crash bug.

Edit: It didn't dawn on me that you were wanting to get a random team player, so I tweaked the code to only advance the iteration count when the current player matches the requirements.
« Last Edit: December 13, 2007, 07:47:14 PM by vloktboky »
Logged

Reborn

  • Donator
  • Offline Offline
  • Posts: 200
    • View Profile
Re: Getting a random player revisited ~ Dirty crash on next map load
« Reply #3 on: December 14, 2007, 09:06:36 AM »

Thankyou for the code and your time vlok, I really appreciate it :-)

Check this out:
http://www.mp-gaming.com/reborn/movies/SSHTP.wmv
Logged
Pages: [1]   Go Up