This was my first mod longer than one line of code in length. Naturally, it lacks the subtlety of my other mods, but uses strings and booleans very effectively. I spent two or so weeks working on this mod, as compared to the one to three days I've spent on the other ones. This guide is NOT done in the order of when events happen, but instead is done in the logical order of when each piece should be made and/or how I originally made the mod remake.
Something to note:In this mod, I don't create my strings onload like usual. In fact, such a method wouldn't even work. Everything in it is based on the triggers and how they work. Essentially, none of it exists until somebody enters the room. I made this mod based primarily off of picklefish stats and used many of its methods within. I also used much of what already existed. Darkager taught me about *('string' # param) and without this method, I could not have completed the mod. Therefore, big thanks to picklefish, Darkager and Fatal150.
LobbyPlayerList
LobbyPlayerList is a strange beast, especially considering how it looks and what it does in the actual code. To a new modder, it can be quite daunting. I spent days trying to get strings to change at the right time in response to it. However, from this I learned one important thing: You don't have to use S2's framework or do anything like what they did. Watches provide you information. It can be seen from any loaded interface.
Code:
watch="LobbyPlayerList"
ontrigger="
If(param0 == -1, ClearItems());
EraseListItemByValue(param0);
If((param0 != -1), Split(
AddTemplateListItem(
'chat_list_entry',
param0,
'username', param1,
'color', param2,
'host', param3,
'loading', param4,
'team', param5,
'referee', param6,
'loadpercent', param7,
'staff', param8,
'premium', param9
),
CreateString('_chat_user_' # param0 # '_name', param1),
SortListboxValue())
);
"
LobbyPlayerList works by spamming over and over again, at least when a player is loading. This is because it's the trigger responsible for assigning the percentage loaded to a player. It's special in that everything used by it is clearly labeled or it is otherwise immediately obvious what it does. This is what we'll use to make everything happen, since it's persistent and gives us the names of every player who's in the game, regardless of whether or not they have taken a slot.
Organization
We're going to break up the tasks based on triggers. Why base it on triggers? Well, I didn't know about events yet. Also, triggers are very nice for passing parameters without keeping tracks of specific names or anything like that. That way you don't have to remember that psr_CurrentSelectedUser represents the player's name without his clan tag.
Speaking of which, this should only go off when param0 would allow regular names to go off. We also like param1 because it's a player's name and param3 because it's a player's host status (so we don't try to kick ourselves). So, let's bring those with us. What each of these are is made obvious by the code written for game_chat.package.
Code:
watch2="LobbyPlayerList"
ontrigger2="
If(!(param0==-1),
Trigger('ListRenamer',param1,param3)
);"
Removing Clan Tags
It'll only get the stats of a player if the name you give it includes the clan tag, but it wont kick a player unless the name doesn't include the clan tag. This is something that becomes obvious pretty fast when someone who is in a clan joins your game, since it'll either freak out when trying to get their stats or fail to kick them. So, we'll have to learn a bit about strings to do this. You should already know about the XAML reference. It can help greatly with learning what you should do about strings. We're going to want to take a substring. Each clan name ends with a ], so we should search for that bracket and take everything after it to get the player's name. We can set the length of our substring based on the length of the string minus the position on the string the bracket is at plus 1 (since the substring locations start at 0).
Code:
watch3="ListRenamer"
ontrigger3="
CreateString('psr_CurrentSelectedUser',
'' # substring(
param0,
searchstring( param0, ']', 0)+1,
stringlength(param0) - (searchstring( param0, ']', 0)+1)
)
);
CreateString('username_' # psr_CurrentSelectedUser, param0);
Trigger('ListWatcher', psr_CurrentSelectedUser,param1);
"
Getting Player Stats
Obviously, we'll need to get the player's stats if we want to see their PSR, since it isn't given to us until they take a slot. GetPlayerStatsName wont work right if you spam it. Unfortunately, LobbyPlayerList does nothing but spam, so we need to create something to keep it from spamming. We're also going to give it special behavior on the first time it runs through a player for this method. It'll start everything for us in this manner. In order to get everything to work properly, quite a few filters will have to be made.
Code:
watch4="ListWatcher"
ontrigger4="
If(StringEmpty(*('kickcheck_' # param0)),
If(StringEmpty(antispam),
Split(
CreateString('kickcheck_' # param0,'pass'),
CreateInt('emload_' # param0, -1),
CreateInt('reload_' # param0, psr_filter_options_minval),
CreateInt('cap_' # param0, 0),
CreateString('antispam','activate'),
GetPlayerStatsName(*('username_' # param0))
)
),
If(StringEquals(*('kickcheck_' # param0),'fail'),
If(*('cap_' # param0) lt 100,
If(psr_filter_options_enabled,
Trigger('StringMaker',param0,param1)
)
)
)
);"
kickcheck is used to determine what the mod should do with the player. If it doesn't exist, it's made and his stats are gotten. Since the mod is designed to get the player's stats every time the first time it runs through, it simply changes the value of kickcheck from 'pass' to 'fail' after it gets the player's stats. It'll then be set to 'end' if it is determined that a player should not be kicked or keep it as 'fail' if they are to be kicked upon return.
cap is there to ensure that this program never falls into an infinite loop, especially when you are not host. It will rarely reach its maximum unless you play a game with someone you would not normally allow in your game. This mod still searches for their stats because it speeds up picklefish stats, removing the short delay it usually takes to find a player's stats.
emload and reload are created now so that the player's information can be put into them later. We have no reason to make these for players outside of the game lobby, so we'll make these here, instead of in PlayerStats.
antispam is a mechanism I built to stop the spamming of this. It gets set to be empty in response to PlayerStats. It keeps the mod from activating at all for new players until the current player has finished. This way, it can't look up one players stats until it's done with someone elses. Why is this important you say? When will two players ever appear at the exact same time? Well, to you I say, you're right, this situation is very rare. However, it can happen if players manage to get into the game before the host does. This way, instead of just looking up the last player, it'll look up each player in order.
PlayerStats
Now, we're going to make some changes in strings in response to player stats. Everything in here is data collection for use in the player kicker. Nothing about it is clever, outside the fact that this is where antispam is set back to empty. The string for the announcer is created here, which is completely arbitrary and probably because it was 'tacked on' later.
Code:
watch="PlayerStats"
ontrigger="
Set('antispam','');
If(psr_filter_options_enabled,
If(!StringEmpty(*('kickcheck_' # param27)),
If(StringEquals(*('kickcheck_' # param27),'pass'),
Split(
Set('reload_' # param27,param35),
Set('emload_' # param27,(param37/(param0+param1))*100),
Set('kickcheck_' # param27,'fail'),
CreateString('announcer' # param27,'empty')
)
)
)
);
"
The only thing that's done here that isn't exclusive to the kick being enabled in setting antispam to empty. This is because the mod still runs even if you are not host so that picklefish stats can use it to gather player stats.
It's also rather unnecessary that I check if kickcheck is empty before I compare it to 'pass'. However, it is very important to make sure that it is set to 'pass' when this happens because if it's set to 'end' or 'fail' the mod could spontaneously kick someone. This is because this section changes kickcheck to 'fail'. There's no reason to record the stats of someone who we already looked up. I mean, sure, we could, but it's unlikely that someone's stats are going to change massively in one game instance.
param35 is psr. param37 is number of em games. param0 and param1 are wins and losses. emload is multiplied by a hundred for better ease of use in the user interface. This way, players simply input a percent instead of a float between 0 and 1.
The Player Kicker
Next, we're making the actual mechanism for kicking the player. This gets a little complicated because it also sets the strings for the announcer. This section is going to get cut up into pieces because it's quite long.
Code:
watch5="StringMaker"
ontrigger5="
CreateString(param0 # 'minpsr', '');
CreateString(param0 # 'maxpsr', '');
CreateString(param0 # 'minem', '');
CreateString(param0 # 'maxem', '');
CreateString(param0 # 'and1', '');
CreateString(param0 # 'and2', '');
CreateString(param0 # 'and3', '');
If(!param1,
Trigger('PlayerKicker',param0),
Set('kickcheck_' # param0, 'end')
);"
Stringmaker initializes the strings necessary for the announcer and also prevents the playerkicker from triggering if someone is host. This way, it goes through everything except the playerkicker for them.
Code:
watch6="PlayerKicker"
ontrigger6="
Set('kickcheck_' # param0,'end');
Set('cap_' # param0, *('cap_' # param0)+1);
if(!IsBuddy(param0),
if(!StringEquals(param0, psr_filter_options_exception1),
if(!StringEquals(param0, psr_filter_options_exception2),
if(!StringEquals(param0, psr_filter_options_exception3),
Here we set kickcheck to end so that it doesn't go off again. We also add 1 to the cap (this equates to about 12 in reality due to how fast this spams). We check to make sure the person we're kicking is neither a buddy of the host's, nor on the player exceptions list.
Code:
Split(
if(psr_filter_options_minpsr,
if(*('reload_' # param0) lt psr_filter_options_minval,
Split(
Kick(*('username_' # param0)),
Set('kickcheck_' # param0,'fail'),
Set(param0 # 'minpsr','his PSR of ' # *('reload_' # param0) # ' was too low')
),
Set(param0 # 'minpsr','')
)
),
if(psr_filter_options_maxpsr,
if(*('reload_' # param0) gt psr_filter_options_maxval,
Split(
Kick(*('username_' # param0)),
Set('kickcheck_' # param0,'fail'),
Set(param0 # 'maxpsr','his PSR of ' # *('reload_' # param0) # ' was too high')
),
Set(param0 # 'maxpsr','')
)
),
if(psr_filter_options_maxem,
if(*('emload_' # param0) gt psr_filter_options_maxvalem,
Split(
Kick(*('username_' # param0)),
Set('kickcheck_' # param0,'fail'),
Set(param0 # 'maxem','his EM% of ' # *('emload_' # param0) # '% was too high')
),
Set(param0 # 'maxem','')
)
),
if(psr_filter_options_minem,
if(*('emload_' # param0) lt psr_filter_options_minvalem,
Split(
Kick(*('username_' # param0)),
Set('kickcheck_' # param0,'fail'),
Set(param0 # 'minem','his EM% of ' # *('emload_' # param0) # '% was too low')
),
Set(param0 # 'minem','')
)
),
There are four separate methods with which you can kick someone. Either their PSR is too high or too low or their EM% is too high or too low. Each one has a corresponding string that will be set for the announcer. It's okay to kick a player multiple times.
Each section works as follows: If the player meets the requirements to be kicked, that player will be kicked, his kickcheck set to fail and a string set for the announcer. If he did not fail that check, the announcement for that check will be set to empty. In this section, it is very important to understand that *('emload_' # param0) will return emload_Ignorance which is 0 because I don't play EM. That way, it shows the actual EM% in the announcer string.
Code:
If(IsHost(),
If(psr_filter_options_announcer,
If(StringEquals(*('kickcheck_' # param0),'fail'),
Split(
If((!StringEmpty(*(param0 # 'minpsr')) and
(!StringEmpty(*(param0 # 'maxpsr')) or !StringEmpty(*(param0 # 'minem')) or !StringEmpty(*(param0 # 'maxem')))),
Set(param0 # 'and1', ' and '),
Set(param0 # 'and1', '')),
If((!StringEmpty(*(param0 # 'maxpsr')) and
(!StringEmpty(*(param0 # 'minem')) or !StringEmpty(*(param0 # 'maxem')))),
Set(param0 # 'and2', ' and '),
Set(param0 # 'and2', '')
),
If((!StringEmpty(*(param0 # 'minem')) and !StringEmpty(*(param0 # 'maxem'))),
Set(param0 # 'and3', ' and '),
Set(param0 # 'and3', '')
)
)
)
)
);
This only happens if you're the host, if the annoucer is activated and if the player is to be kicked. This section is for determining when to post 'and' in the message given by announcer.
The first and requires that the first reason to be kicked is active, as well as at least one other reason. The second requires the second reason to be active as well as either the third or the fourth. The third requires the third and the fourth reasons to be active, which shouldn't ordinarily happen. If the conditions aren't met, the ands are set to be empty. This way, the ands will only appear when they are necessary. We want the ands to be speciifc to the presence of the strings that describe whether or not a player is kicked, so we use those to determine whether or not there should be an and present.
Code:
If(IsHost(),
If(psr_filter_options_announcer,
If(StringEquals(*('kickcheck_' # param0),'fail'),
If(StringEquals(*('announcer' # param0), 'empty'),
Split(
AllChat(
'PSR filter has kicked ' # param0 # ' because ' #
*(param0 # 'minpsr') # *(param0 # 'and1') #
*(param0 # 'maxpsr') # *(param0 # 'and2') #
*(param0 # 'minem') # *(param0 # 'and3') #
*(param0 # 'maxem') # '.'
),
Set('announcer' # param0, 'full')
)
)
)
)
);
"
Next, we'll post the string to AllChat. It has similar requirements to the previous line, except that it also requires that announcer for this player has not yet occurred. This is required to prevent potential spamming of the AllChat, although it also prevents returning players kicks to be announced. The reason that this is placed into a separate line despite the same conditions to the previous line is because strings are not saved until end of line statements occur, generally. Also, it vastly improves the organization of the mod. We concatenate all of the strings that were made previously and put them in the correct place in our announcement. We then set announcer # param0 to 'full' in order to prevent it from occurring again. Yes, I know I could've used a bool, but this was back when I was a new modder so too bad it's me
.
That's it for the actual mechanism for the PSR Filter Remake. Don't forget to declare your triggers at the top.
Code:
<trigger name="ListRenamer" />
<trigger name="ListWatcher" />
<trigger name="StringMaker" />
<trigger name="PlayerKicker" />