Announcement

Collapse
No announcement yet.

Creating a Reserved Slot Mutator

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Creating a Reserved Slot Mutator

    Note: This is for UT2004.

    Is it possible to access GameInfo::PreLogin or AccessControl::PreLogin from the context of a mutator? I'm trying to set up a mutator that does reserved slots and that seems to be the right function to use, but Mutator doesn't inherit it, and I don't know how to make it inherit it.

    Here's my code so far:

    Code:
    class MutReservedSlot extends Mutator;
    
    function PostBeginPlay()
    {
    	Super.PostBeginPlay();
    	Log("Reserved Slot mutator initialized");
    }
    
    function PreLogin
    (
        string Options,
        string Address,
        string PlayerID,
        out string Error,
        out string FailCode
    )
    {
    	if (PlayerID == "[my playerID]")
    	{
    		Log("Oh my goodness!");
    	}
    
    	//Super.PreLogin(Options, Address, PlayerID, Error, FailCode);
    }
    
    defaultproperties
    {
    	GroupName="ReservedSlot"
    	FriendlyName="Reserved Slot System"
    	Description="Truly awful!"
    }
    The first Log() works fine, but the second one obviously doesn't. Uncommenting the Super.PreLogin() causes errors as well because it's not part of Mutator.

    Should I be using PreLogin even? I just need a quick way of intercepting incoming connections and getting their playerID. Preferrably before they've spawned.

    #2
    There are three ways for getting your function called:
    • Your function is an event called from native code,
    • your function is called from the GameInfo's or AccessControl's PreLogin or
    • your function is assigned to a delegate called through one of the two way above.
    None of these apply to PreLogin, though. You will not be able to hook PreLogin without a GameInfo or AccessControl class.

    Comment


      #3
      So is there any way to inherit from either class? If not, can I even do this sort of thing in the context of a mutator? Are there any similar functions that I can use in the context of a mutator to accomplish what I've described?

      Comment


        #4
        It won't be as simple as you want.
        You can use the ModifyLogin function in the Mutator class to detect a player login. However, at that point you can't access the correct GUID. You need to remember the player who just logged in (e.g. in a dynamic array of PlayerControllers, in case more than one player logs in) and check the GUID in Tick() or Timer().

        Comment


          #5
          Well, after some investigation, it seems Mutator::OverrideDownload will give me a player's playerid as they connect, though I'm not sure if I should be using that function for that purpose.

          My code as of now:

          Code:
          class MutReservedSlot extends Mutator;
          
          function PostBeginPlay()
          {
          	Super.PostBeginPlay();
          	Log("MutReservedSlot initialized");
          }
          
          function bool OverrideDownload
          (
          	string PlayerIP,
          	string PlayerID,
          	string PlayerURL,
          	out string RedirectURL
          )
          {
          	Log
          	(
          		"MutReservedSlot: OverrideDownloaded triggered, "
          		$PlayerIP$", "
          		$PlayerID
          	);
          
          	if (PlayerID == "[my playerid]")
          	{
          		Log("MutReservedSlot: author detected!");
          	}
          
          	Super.OverrideDownload(PlayerIP, PlayerID, PlayerURL, RedirectURL);
          
          	return true;
          
          }
          
          defaultproperties
          {
          	GroupName="ReservedSlot"
          	FriendlyName="Reserved Slot System"
          	Description="Truly awful!"
          }
          I could probably start to work on the actual reserved slot functionality, but there's one problem. These playerids of people with slots, they're stored in a MySQL database. Is it possible to connect to a MySQL server with UnrealScript?

          I've done this in Half-Life 2 without any problems, as it's all C++, but I have no idea how this would be done in UnrealScript.

          Comment


            #6
            Okay, I decided to just use a PHP script to interface with the database and use LibHTTP to interface with the PHP script.

            It seems to work, except that even though it's getting the data that the script returns, socket.ReturnData is empty.

            Code:
            ScriptLog: MutReservedSlot: OverrideDownloaded triggered, [my ip address]:2034, [my playerid], Index.ut2?LAN?Name=dackz?Character=Selig,
            LibHTTP: HttpSock[2] : ParseRequestUrl 	 RequestLocation 	 [address]/?type=ut2004&id=[my player id]
            LibHTTP: HttpSock[2] : ParseRequestUrl 	 sHostname 	 [hostname]
            LibHTTP: HttpSock[1] : Changing remote port to default (80) 	 0	 
            LibHTTP: HttpSock[2] : Socket created 	 DM-Phobos2.HttpLink
            Log: Resolving [hostname]...
            ScriptLog: MutReservedSlot: ValidatePlayerID() called successfully
            Log: Resolved [hostname] ([hostname's ip])
            LibHTTP: HttpSock[2] : Host resolved succesfully 	 [hostname]
            LibHTTP: HttpSock[2] : Local port succesfully bound 	 2036
            LibHTTP: HttpSock[2] : Opening connection
            LibHTTP: HttpSock[2] : Connection established
            LibHTTP: HttpSock[3] : Send data 	 False 71 	 GET [address]/?type=ut2004&id=[my player id] HTTP/1.1
            LibHTTP: HttpSock[3] : Send data 	 False 21 	 Host: [hostname]
            LibHTTP: HttpSock[3] : Send data 	 False 96 	 User-Agent: LibHTTP/352 (UnrealEngine2; build  3339; http://wiki.beyondunreal.com/wiki/LibHTTP )
            LibHTTP: HttpSock[3] : Send data 	 False 17 	 Connection: close
            LibHTTP: HttpSock[3] : Send data 	 False 14 	 Accept: text/*
            LibHTTP: HttpSock[3] : Send data 	 True 0
            LibHTTP: HttpSock[2] : Request send 
            LibHTTP: HttpSock[3] : Received data 	 True 15 	 HTTP/1.1 200 OK
            LibHTTP: HttpSock[3] : Received data 	 True 35 	 Date: Wed, 27 Apr 2005 00:24:49 GMT
            LibHTTP: HttpSock[2] : Server date 	 1114561489 	 Wed, 27 Apr 2005 00:24:49 GMT
            LibHTTP: HttpSock[2] : Timezone offset 	 -4
            LibHTTP: HttpSock[3] : Received data 	 True 190 	 Server: Apache/1.3.33 (Unix) [...]
            LibHTTP: HttpSock[3] : Received data 	 True 17 	 Connection: close
            LibHTTP: HttpSock[3] : Received data 	 True 26 	 Transfer-Encoding: chunked
            LibHTTP: HttpSock[2] : Body is chunked 	 True
            LibHTTP: HttpSock[3] : Received data 	 True 24 	 Content-Type: text/plain
            LibHTTP: HttpSock[3] : Received data 	 True 0
            LibHTTP: HttpSock[3] : Received data 	 False 3 	 5
            LibHTTP: HttpSock[2] : Next chunk 	 5
            LibHTTP: HttpSock[3] : Received data 	 False 5 	 dackz
            LibHTTP: HttpSock[2] : Next chunk 	 -1
            LibHTTP: HttpSock[3] : Received data 	 False 1 	 0
            LibHTTP: HttpSock[2] : Next chunk 	 0 
            LibHTTP: HttpSock[3] : Received data 	 False 0
            LibHTTP: HttpSock[2] : Next chunk 	 0
            LibHTTP: HttpSock[2] : Reached maximum iteration count 	 32
            LibHTTP: HttpSock[2] : Connection closed
            That's dumped to the console, and I also have this:

            Code:
            for (i = 0; i < socket.ReturnHeaders.length; i++)
            {
            	Log("MutReservedSlot: "$socket.ReturnHeaders[i]);
            }
            
            for (i = 0; i < socket.ReturnData.length; i++)
            {
            	if (len(socket.ReturnData[i]) > 1024)
            	{
            		Log("MutReservedSlot: "$Left(socket.ReturnData[i], 1024));
            		Log("MutReservedSlot: "$Mid(socket.ReturnData[i], 1024));
            	}
            	else Log("MutReservedSlot: "$socket.ReturnData[i]);
            }
            The headers are printed, but not the data, even though you can see the data in the debug messages (it's colored red above.)

            Code:
            ScriptLog: MutReservedSlot: HTTP/1.1 200 OK
            ScriptLog: MutReservedSlot: Date: Wed, 27 Apr 2005 00:58:09 GMT
            ScriptLog: MutReservedSlot: Server: Apache/1.3.33 (Unix) [...]
            ScriptLog: MutReservedSlot: Connection: close
            ScriptLog: MutReservedSlot: Transfer-Encoding: chunked
            ScriptLog: MutReservedSlot: Content-Type: text/plain
            That and the only other problems I'm having is that I'm not sure how to kick someone or drop their connection in the context of Mutator::OverrideDownload(). Obviously I have their IP address and PlayerID, but I have no idea what functions I can use to drop them with just those two pieces of information.

            Edit: Changing the PHP script to echo the username and then a newline seemed to change the above:

            Code:
            echo "$result\n";
            Now I get:

            Code:
            ScriptLog: MutReservedSlot: HTTP/1.1 200 OK
            ScriptLog: MutReservedSlot: Date: Wed, 27 Apr 2005 01:15:43 GMT
            ScriptLog: MutReservedSlot: Server: Apache/1.3.33 (Unix) [...]
            ScriptLog: MutReservedSlot: Connection: close
            ScriptLog: MutReservedSlot: Transfer-Encoding: chunked
            ScriptLog: MutReservedSlot: Content-Type: text/plain
            ScriptLog: MutReservedSlot: dackz
            I'm not sure if that means socket.ReturnData has "dackz\n" and there was just a problem with the for loop, or if it has "dackz" and there's a problem with how it's deciding what should and shouldn't go in socket.ReturnData.

            Comment


              #7
              because of a bug, for some reason the chunkedCounter is <= 0, so the next data block is seen as a chunk size instead of data.
              I'm unable to figure out how this happened

              Comment


                #8
                Originally posted by El_Muerte_[TDS]
                because of a bug, for some reason the chunkedCounter is <= 0, so the next data block is seen as a chunk size instead of data.
                I'm unable to figure out how this happened
                Well what do you suggest I do? All I need is the data itself in a string variable.

                Comment


                  #9
                  just make sure you end it with a newline, newlines and carriage returns are stripped from the ReturnData

                  Comment


                    #10
                    Okay, that sounds good.

                    I'm still having problems trying to kick people when they connect. I can't find any function that allows me to kick people who haven't spawned yet.

                    I've found that by the time ModifyPlayer() is called, the person has a PlayerController. At ModifyLogin() and OverrideDownload() they don't have a PlayerController.

                    I have been able to kick myself before I completely spawned though, through the webadmin interface. It reported me as a spectator though my game did not fully load. I'm not sure exactly how far along with loading I was though.

                    Anyway, is there any way to manually send a kick message to a player before they have a PlayerController? Or is there any way for me to get access to their PlayerController before they have spawned, if it even exists? (Maybe I could assign the person a PlayerController manually and then use it to kick them before the game itself assigns them a PlayerController?)

                    Comment


                      #11
                      The spawning of a PlayerController will cause CheckReplacement() to be called, like for any other actor. However, at that point you can't associate that PlayerController actor with a GUID. You need to wait at least until the next Tick().

                      Comment


                        #12
                        Originally posted by Wormbo
                        The spawning of a PlayerController will cause CheckReplacement() to be called, like for any other actor. However, at that point you can't associate that PlayerController actor with a GUID. You need to wait at least until the next Tick().
                        Okay, I'm not entirely sure how you mean for this to be done, but this is what I've come up with so far:

                        Code:
                        var bool bPlayerConnected;
                        var bool bPlayerSpawned;
                        var Actor NewPlayer;
                        
                        function PostBeginPlay()
                        {
                        	Disable('Tick');
                        
                        	[...]
                        
                        	Super.PostBeginPlay();
                        }
                        
                        function bool OverrideDownload
                        (
                        	string PlayerIP,
                        	string PlayerID,
                        	string PlayerURL,
                        	out string RedirectURL
                        )
                        {
                        	[...]
                        
                        	bPlayerConnected = true;
                        
                        	[...]
                        
                        	return Super.OverrideDownload(PlayerIP, PlayerID, PlayerURL, RedirectURL);
                        }
                        
                        function bool CheckReplacement(Actor Other, out byte bSuperRelevant)
                        {
                        	if (bPlayerConnected == true)
                        	{
                        		bPlayerConnected = false;
                        		bPlayerSpawned = true;
                        		NewPlayer = Other;
                        
                        		Enable('Tick');
                        	}
                        
                        	return Super.CheckReplacement(Other, bSuperRelevant);
                        }
                        
                        function Tick(float DeltaTime)
                        {
                        	if (bPlayerSpawned == true)
                        	{
                        		bPlayerSpawned = false;
                        
                        		Log
                        		(
                        			"MutReservedSlot: Tick() after CheckReplacement() triggered, "
                        			$NewPlayer.bNetOwner
                        		);
                        	}
                        
                        	Disable('Tick');
                        }
                        Now, first of all, am I doing this right by declaring function Tick() and adding the if statement? That seems like it would be an unecessary performance hit, no matter how small or trivial.

                        Would calling Tick() inside CheckReplacement(), and then dealing with Actor have the same effect, or would it not?

                        Also, how do I get the PlayerController of the Actor? I couldn't find anything while looking through the source.

                        Edit: I read up some more at UnrealWiki and just learned about Enable() and Disable(), so I guess that solves the potential performance problem.

                        Edit #2: More reading and this seems to work:

                        Code:
                        Log
                        (
                        	"MutReservedSlot: Tick() after CheckReplacement() triggered, "
                        	$PlayerController(Controller(NewPlayer)).PlayerReplicationInfo.PlayerName$", "
                        	$PlayerController(Controller(NewPlayer)).GetPlayerIDHash()
                        );
                        
                        Player.ClientNetworkMessage("AC_Kicked", "Reserved slot needed"); // Hooray!
                        The only thing that bothers me now is that bPlayerConnected is being compared A LOT during the server startup and probably on every map load. I'm not sure that's a good thing but since it's not a probe function I can't disable it.

                        Also, am I creating NewPlayer properly? It seems to be referencing the object, not creating a new object, as the kick works, but I'm not sure if I should be resetting NewPlayer to save resources when I'm done using it.

                        Comment


                          #13
                          Well I have another problem now. I need to get GameInfo's MaxPlayers and NumPlayers/BotPlayers variables so I know when I need to kick a non-reserved slot person.

                          The problem with that is yet again I don't have access to GameInfo, so I can't get it. I don't see it defined anywhere else, so I'm out of ideas as to how I'm going to get those variables.

                          Edit: Well I just feel silly now. Level.Game.MaxPlayers works.

                          Comment


                            #14
                            After some testing, the reserved slot functionality works to an extent, but I'm having some problems.

                            When multiple players connect at nearly the same time, the script only validates the first one. I was thinking this was because my HttpSock object was only able to do one GET at a time, so I made an array and initialized an HttpSock object into the array for each connecting player, but it's still not executing the multiple queries.

                            That, and it doesn't seem to always kick a public user when the server is full.

                            I'm also not sure if my player array is staying sane during map changes. The mutator doesn't say NotifyLogout() happened during map change, which is when I remove players from my player array, yet OverrideDownload/CheckReplacement are still called and there aren't leftover player structs from the previous map.

                            And besides those problems, I'm wanting to create console commands for server operators and in-game commands for players. I've been looking at that wiki and found "exec function" but apparently it doesn't work in the context of a mutator, and I have absolutely no idea how to create commands for players.

                            Here's the mutator's source if anyone wants to take a look at it. Perhaps someone knows why I'm having the problems I've described.

                            http://dackz.net/misc/code/MutReserv...eservedSlot.uc

                            Comment

                            Working...
                            X