ID:148817
 
I'd like to have AI take over when a player quits in the middle of a game. I tried creating a new mob, then setting it's key to that of the player who is quitting, in hopes his mob will then be a clientless mob (with all variables, possessions, etc. intact) that will now be AI, while the new mob that the person took over can gracefully leave.

I got a ton of run-time errors/glitches when I tried this approach though. The code is shown below. Is there a demo/tutorial that would help with this, or does anyone have any advice on the matter? Thanks.

mob/player/proc/leave_game()
if(!game_over)
var/mob/player/spectator = new(src.loc,0)
spectator.key = src.key // make the switch!
src.name = "[src.name]_AI"
spectator.name = spectator.key
world << "[src.name] takes over."
From experience, I can tell you this is harder than it sounds. The AI has to have all the vars set just right, and it needs to know exactly what the player was doing in case it needs to finish selecting from a pop-up or such.

The only real advice I can offer is to consider using a datum to hold all key info about your player, and attach it to different mobs as needed.

Lummox JR
That approach can get hairy really fast. I initially did something similar in Una, and while it eventually worked, it wasn't pretty, and was probably still prone to glitches under unanticipated circumstances.

Then I had a bit of a revelation. Forget all this mob/key changing stuff - that's what was giving me so many headaches. Instead I moved all of the AI into normal mob procs. Then at the beginning of any proc that needs something done, I check to see if the mob has a client. If so, do any necessary setup and allow the player to make a play. Otherwise, call the AI routine to do it.

So for example, when a player's turn starts, the main game logic calls mob.DoTurn(). That proc looks something like this:
mob/proc/DoTurn()
if (!src.client || src.auto) return src.cpuDoTurn() // call AI
var/obj/card/p = src.FindPlayable() // otherwise look for a playable card
if (!p) // nothing playable
src.Draw() // draw a new card
else
trn.Active() // otherwise let him play!

mob/proc/cpuDoTurn()
// what does the computer want to do this turn?

That's a very simplified version but you get the idea. Any other procs that need to behave differently for AI vs live players will need the same sort of thing.

You may notice I also have a mob/auto variable. As you may guess, it's true if the player is set to automatic play (logged in but AI-controlled), false if not. This allows players to set automatic mode and then just sit back and watch (or take a potty break). You certainly don't need that part, but it's useful for setting an idle timer for someone who's been distracted from the computer. If someone is idle too long, I set auto to true and have the computer play for them so the game can keep moving.

Anyway I hope this helps.
In response to Air Mapster
That's exactly the approach I've put into Kwijibo as well. Working pretty well, and I don't have to worry much about whether someone is in or out of the game.
In response to Air Mapster
hmmm... it seems I gave you the impression that my ai mobs are a different mob type than real players, which is not the case.

also, every player action requiring a decision checks to see if usr.client is null.

I think i'll be able to fix this now based on the info in your post.

thanks.
In response to Air Mapster
one further question about your approach:

what do you do when a player leaves the game, but wants to remain logged in to the world? is this even an option? I think this is the part that was causing trouble for me... trying to create a new mob so the person doesn't get booted out of the game. I suppose if someone is quitting in the middle in the game, it's rare that they are sticking around to chat...
In response to Dramstud
dramstud wrote:
one further question about your approach:

what do you do when a player leaves the game, but wants to remain logged in to the world? is this even an option? I think this is the part that was causing trouble for me... trying to create a new mob so the person doesn't get booted out of the game.

Ah, I see. I saw that you were setting key variables and thought you were completely switching mobs to change between AI/live players.

The closest thing I had to this in Una, was a player could set \his auto flag to true, thereby making the computer play for him. The player is effectively out of the game, but still in the world to chat. However, this doesn't address the situation where another person is waiting to join the game and would like to take over for the departed player (or for any AI player). Then it does get a little hairy.

Though depending on how you have the game controlling code setup, I would avoid trying to switch keys if you want to make this possible. That's some sort of black voodoo magic, there. Instead, as Lummox JR suggested, I would simply copy all all pertinent mob variables from the departing mob to the new one, and then replace the old one with the new one in the turn rotation. As he said, you could put all of these variables in a single datum under /mob, making the transition fairly painless.

Una does do this - if a new player enters the world and there are currently AI players in the game, that person is asked if \he wants to join and take over for a computer player. One thing it doesn't do is allow people to join or leave the game at will while staying in the world. Implementing this would be more of the same - I just hadn't thought of allowing it at the time I wrote the game.
In response to Air Mapster
I decided against letting people leave a game in the middle, yet still remain in the world. They can simply enable their autopilot if they don't want to play.

The autopilot works great. Thanks for the suggestion, Mapster!

I noticed an interesting side effect I wasn't expecting. A player logged out, so AI took over for him. However, he came back in the world a few minutes later and smoothly took over for the AI again. He said something seemed different, but wouldn't give me details. However, the game seemed to flow properly. In this situation, is the mob/Login() code run before or after the player takes over his old mob (or is done during Login() -- ie. the ..() if I rewrote Login()?


In response to Dramstud
mob/Login() is called by client/New() after the client is connected to its mob. Note that Login() is not called only when someone connects to the world, but any time a client connects to a new mob. This is all explained in the helpfully buried document http://www.byond.com/docs/tutorial/connect/connect.html

The nice side effect of having the player seamlessly take over upon reconnection was completely intended. Since you didn't delete the mob upon logout, it remained in the world with the same key as the player formerly connected. Upon reconnect, BYOND looked for a mob with the same key as the client, found one, and automatically connected the mob and the client. Next turn, client is not null, so the person gets to play as normal.

The one thing you might be careful about is Login(). Just be aware that it does get called again when the person reconnects, so you don't want to do anything like reinitialize variables that may be important in the middle of the game.
In response to Air Mapster

The one thing you might be careful about is Login(). Just be aware that it does get called again when the person reconnects, so you don't want to do anything like reinitialize variables that may be important in the middle of the game.

It looks like I'm okay on variables. I have a bunch of usr << browse_rsc() commands in Login() so users can view the rules web page, etc. I assume these are still necesary if someone quits and rejoins the world, right?
In response to Dramstud
dramstud wrote:
I have a bunch of usr << browse_rsc() commands in Login() so users can view the rules web page, etc. I assume these are still necesary if someone quits and rejoins the world, right?

The browse_rsc() calls probably aren't necessary, but they don't hurt and if the person somehow deleted those files in between connections (they'd have to know where to look), they would be necessary. So I'd say leave them in.

One nice touch you could do is make a mob variable - something like already_logged_in - and set that to true at the very end of Login(). Then at the beginning, check that variable and if true, you can say "Welcome back!" instead of whatever you normally tell them.