//******************************************************************************************************
// ProconRulz Procon plugin, by bambam, 45 from Kontra
//******************************************************************************************************
/* Copyright 2013 Ian Forster-Lewis
This file is part of my ProconRulz plugin for ProCon.
ProconRulz plugin for ProCon is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ProconRulz plugin for ProCon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ProconRulz plugin for ProCon. If not, see .
*/
#region release notes
// v45 - a. support for multiple conditions in an if statement with AND, ~AND, OR, ~OR
// b. added %random% to return a random double between 1 and 0.
// c. added 'On ServerSay' event that allows rulz to be accessed using RCON console. Other plugins can now communicate with your rulz via chat commands. EX. /runFunction1 said by xVotemap...
// d. added support for regex in If conditions using 'If Value regx Pattern'
// e. added %textchannel% and Channel [global,team,all] to monitor what channel a message came via.
// v44 - a. support for BF4
// b. support for rulz files (Plugins\BF4\proconrulz_*.txt)
// c. 'Not' modifier now also allowed with If and Text conditions
// d. added VictimTeamKey
// e. Enable/Disable rulz.txt files in settings
// f. Linux support for external rulz files (credit FritzE)
// g. On Init trigger added, for var initialisations
// h. On RoundOver trigger added. Bugfix for NULL team
// j. Update to reload .txt rulz files on plugin enable, and display full path if not found
// v43 - a. added 'rounding' to vars, e.g. %x.3% is 3 decimal places
// b. Allow "Set %ini% 0" to reset ini file, Set "%ini_% 0" to delete section,
// c. bugfix for TargetPlayer condition when %targettext% is null. Added '+' for rule continuation
// d. added %score% var for each player (aka %server_score[]%)
// v42 - a. Support %ini_XXX% variables stored in settings
// b. New 'logging' options in plugin settings
// c. arithmetic in Set clauses e.g. Set %x% %x%+1
// d. another effort at the procon URLencode settings work-around
// e. allow spaces in arithmetic e.g. "If %x% + %y% > 10;"
// f. new subst vars %ts1%, %ts2%, %pts% for teamsize 1, 2, playerteamsize
// g. new subst var %hms% for time, %seconds% for time in seconds, %ymd% for yyyy-mm-dd
// v41 - a. Allow parsing of chat text e.g. to pick out a ban reason
// v40 - a. Quoted strings in Exec e.g. Exec vars.serverName "Example server name - Admins Online"
// b. Quoted strings in Set e.g. Set %a% "hello world", $..$ for subst vars
// c. $ replaced with % in rulz_vars to avoid Procon settings encode bug
// d. fix for admin kill triggering On Suicide
// v39 - a. TeamSay, SquadSay, TeamYell, SquadYell,
// %pcountry%, %pcountrykey%, %vcountry%, %vcountrykey% (country codes for player and victim)
// b. Allow 'Exec' actions to call Procon commands
// Allow 'int' value for yell delay (seconds) in Yell actions
// c. Ban and TempBan ban by 'name' if no GUID available
// d. %team_score% aka %server_team_score[]%
// v38 - a. actions and conditions can be mixed in any order,
// b. %streak% is shorthand for %server_streak[%p%]%,
// %team_streak% is %server_team_streak[%ptk%]%
// %squad_streak% is %server_squad_streak[%ptk%][%psk%]%
// rulz vars can be nested using brackets, e.g. %kills[%weapon%]%
// c. modify Exec action to support punkBuster commands
// d. TargetActions are now immediate, TargetPlayer only succeeds IFF 1 player found
// e. Yell delay (default 5 seconds) added to plugin settings
// v37 - string vars (as well as ints from v35)
// b. var names can have embedded subst vars, e.g. "server_%v%_deaths" is a var name with a player name embedded
// c. moved the subst vars processing into assign_keywords
// d. moved most of the Details help text onto the web
// e. bugfix for %c% - set value for actions, temp value for conditions
// f. bugfix for PBBan message
// g. If "%text% word abc" condition,
// allow %vars% in Set %newvar% %p%,%newvar% conditions
// h. added punkBuster.pb_sv_command pb_sv_updbanfile to speed up PB Bans/Kicks
// v36 - Punkbuster bans
// v35 - a. Multiple keys in conditions e.g. "Weapon RPG-7,SMAW"
// b. xyzzy debug
// c. continue
// d. rulz vars Set Incr Decr If
// e. actions thread inline, no decode on rulz, %ps% PlayerSquad
// f. propagate trigger from prior rulz, default continue unless Kill/Kick/TempBan/Ban/End
// v34 - Protected now Admin, Admin_and_Reserved_Slots, Neither
// v33 - BF3 compatibility
// TargetPlayer now auto-TargetConfirm if only one match
// v32 - TargetPlayer,TargetAction,TargetConfirm,TargetCancel, %t% substitution for target
// PlayerCount,TeamCount,ServerCount, %tc% and %sc% substitution for team and server counts
// TempBan action
// Ping condition, %ping% substitution
// PlayerYell,PlayerBoth, AdminSay, %text% substition
// 'Rates' now span round ends
// bugfix for multi-word weapon keys, e.g. "M1A1 Thompson", heli weapons
// bugfix for sp_shotgun_s
// On Join, On Leave triggers
// Whitelist for clans and players
// PlayerFirst, TeamFirst, ServerFirst, PlayerOnce conditions
// Player_loses_item_when_dead bugfix
// v31 - Map, MapMode conditions, On Round trigger, VictimSay action
// (31b bugfix for On Spawn;Damage...)
// (31c bugfix for teamsize)
// v30 - settings options - rules message as PlayerSay, disable rules message
// v29 - TeamKit, TeamDamage, TeamSpec, TeamWeapon counts conditions
// v28 - updated to use latest Procon 1 API
// v27 - "Protected" condition
// v26 - "On Say; Text xxx;" trigger, Headshot %h% substitution
// v25 - "Range N;" condition
// v24 - new conditions Admin (player an admin), Admins (admins on server)
// v23 - fixed Count bug
// v22 - added rule comments ('#' as first char)
// v20 - changed rule to have of Conditions
// v19 - added "Not [Kit|Spec|Damage|Weapon] X" condition
#endregion
#region includes
using System;
using System.Threading;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using PRoCon; // added for FileHostNamePort()
using PRoCon.Core;
using PRoCon.Core.Plugin;
using PRoCon.Core.Players;
using PRoCon.Core.Players.Items;
using PRoCon.Core.Plugin.Commands;
using PRoCon.Core.Accounts;
using PRoCon.Core.Remote; // for FileHostNamePort()
using System.IO;
#endregion
namespace PRoConEvents
{
public class ProconRulz : PRoConPluginAPI, IPRoConPluginInterface
{
public string version = "44j.3a";
#region Types
// the 'Rates' condition keeps track of this many 'rule trigger times' for each player
const int RATE_HISTORY = 60;
private List WhatISaid = new List();
//**********************************************************************************************
//**********************************************************************************************
// DEFINE SOME TYPES
//**********************************************************************************************
//**********************************************************************************************
enum GameIdEnum { BFBC2, MoH, BF3, BF4 }
enum ReserveItemEnum { Player_loses_item_when_dead, Player_reserves_item_until_respawn }
enum LogFileEnum { PluginConsole, Console, Chat, Events, Discard_Log_Messages }
// these are the executable actions
// note that 'TargetAction ' does not actually have
// 'TargetAction' as a defined action, instead it
// is treated as a boolean flag on the PartClass to indicate the action is delayed
enum PartEnum
{ Yell, Say, PlayerYell, PlayerSay, VictimSay, TeamYell, TeamSay, SquadYell, SquadSay,
Log, Both, PlayerBoth, All, AdminSay, // various message actions
// actions affecting 'target' e.g. playername in say text:
TargetConfirm, TargetCancel,
Kill, Kick, PlayerBlock, Ban, TempBan, PBBan, PBKick,
Execute, Continue, End,
Headshot, Protected, Admin, Admins, Team, Teamsize,
Map, MapMode,
Kit, Weapon, Spec, Damage, TeamKit, TeamWeapon, TeamSpec, TeamDamage,
Range,
Count, PlayerCount, TeamCount, ServerCount,
Rate, Text, Channel, TargetPlayer, Ping,
Set, Incr, Decr, Test
};
// flag for rule to fire either on player spawn, or a kill
// Void mean absence of trigger
enum TriggerEnum { Void, Round, RoundOver, Join, Kill, Spawn, TeamKill, Suicide, PlayerBlock, Say, Leave, Init, ServerSay }
enum ItemTypeEnum { None, Kit, Weapon, Spec, Damage }
// substitution values in messages
enum SubstEnum {Player, Victim,
Weapon, WeaponKey, Damage, DamageKey, Spec, SpecKey,
Kit, VictimKit, KitKey,
Count, TeamCount, ServerCount,
PlayerTeam, PlayerSquad,
PlayerTeamKey, PlayerSquadKey,
PlayerCountry, PlayerCountryKey, // from pb info
VictimCountry, VictimCountryKey,
Teamsize, Teamsize1, Teamsize2, PlayerTeamsize,
VictimTeam, VictimTeamKey,Range, BlockedItem, Headshot,
Map, MapMode, Target, Text, Channel, TargetText, Ping, EA_GUID, EA_GUID_Target, PB_GUID, PB_GUID_Target, IP,
Hhmmss, Seconds, Date,
Random
};
static Dictionary> subst_keys = new Dictionary>();
// which players should be 'protected from kicks, kills and bans by ProconRulz
enum ProtectEnum { Admins, Admins_and_Reserved_Slots, Neither };
// a PART is a statement within a rule, i.e. a condition or action
class PartClass
{
// this action should be delayed and applied to another target not current player
public bool target_action;
public PartEnum part_type; // e.g. Kick
public bool negated; // conditions can be 'Not'
public List string_list;
public int int1;
public int int2;
public bool has_count; // true if this part refers to a rule count (%c% or PlayerCount etc)
// e.g. "You were kicked for teamkills". Can also be an integer string for Kill action
public PartClass()
{
target_action = false;
string_list = new List();
int1 = 0;
int2 = 0;
negated = false;
has_count = false;
}
public string ToString()
{
string part_string;
part_string = (negated ? " Not" : "");
switch (part_type)
{
case PartEnum.Headshot:
part_string += " Headshot;";
break;
case PartEnum.Protected:
part_string += " Player protected from kick/kill;";
break;
case PartEnum.Admin:
part_string += " Admin player;";
break;
case PartEnum.Admins:
part_string += " Admins are online;";
break;
case PartEnum.Ping:
part_string += String.Format(" Ping >= {0};", int1);
break;
case PartEnum.Team:
part_string += String.Format(" Team \"{0}\";", keys_join(string_list));
break;
case PartEnum.Teamsize:
part_string += String.Format(" Teamsize <= {0};", int1);
break;
case PartEnum.Map:
part_string += String.Format(" Map name includes \"{0}\";", keys_join(string_list));
break;
case PartEnum.MapMode:
part_string += String.Format(" Map mode is \"{0}\";", keys_join(string_list));
break;
case PartEnum.Kit:
part_string += " Kit \"" +
keys_join(string_list) + (int1 == 0 ? "\";" : String.Format("\" max({0});", int1));
break;
case PartEnum.Weapon:
part_string += " Weapon is \"" +
keys_join(string_list) + (int1 == 0 ? "\";" : String.Format("\" max({0});", int1));
break;
case PartEnum.Spec:
part_string += " Spec \"" +
keys_join(string_list) + (int1 == 0 ? "\";" : String.Format("\" max({0});", int1));
break;
case PartEnum.Damage:
part_string += " Damage \"" +
keys_join(string_list) + (int1 == 0 ? "\";" : String.Format("\" max({0});", int1));
break;
case PartEnum.TeamKit:
part_string += " TeamKit \"" +
keys_join(string_list) + String.Format("\" max({0});", int1);
break;
case PartEnum.TeamWeapon:
part_string += " TeamWeapon \"" +
keys_join(string_list) + String.Format("\" max({0});", int1);
break;
case PartEnum.TeamSpec:
part_string += " TeamSpec \"" +
keys_join(string_list) + String.Format("\" max({0});", int1);
break;
case PartEnum.TeamDamage:
part_string += " TeamDamage \"" +
keys_join(string_list) + String.Format("\" max({0});", int1);
break;
case PartEnum.Range:
part_string += " Range " + String.Format("over {0};", int1);
break;
case PartEnum.Count:
case PartEnum.PlayerCount:
part_string += " Player Rule Count is more than " + String.Format("{0};", int1);
break;
case PartEnum.TeamCount:
part_string += " Team Rule Count is more than " + String.Format("{0};", int1);
break;
case PartEnum.ServerCount:
part_string += " Server Rule Count is more than " + String.Format("{0};", int1);
break;
case PartEnum.Rate:
part_string += " Rate " + String.Format("{0} in {1} seconds;", int1, int2);
break;
case PartEnum.Text:
part_string += " Text " + String.Format("key \"{0}\";", keys_join(string_list));
break;
case PartEnum.Channel:
part_string += " Channel " + String.Format("Speaker channel is \"{0}\";", keys_join(string_list));
break;
case PartEnum.TargetPlayer:
if (string_list != null)
part_string += " Target player contains " +
String.Format("\"{0}\";", keys_join(string_list));
else
part_string += " Target player %t% found;";
break;
case PartEnum.Incr:
part_string += String.Format(" Incr {0};", string_list[0]);
break;
case PartEnum.Decr:
part_string += String.Format(" Decr {0};", string_list[0]);
break;
case PartEnum.Set:
part_string += String.Format(" Set {0};", keys_join(string_list));
break;
case PartEnum.Test:
part_string += String.Format(" If [{0}];", keys_join(string_list));
break;
default:
part_string += (String.Format(" {0}{1} [int: {2}] [string: {3}];",
target_action ? "TargetAction " : "",
Enum.GetName(typeof(PartEnum), part_type),
int1 == null || int1 == 0 ? "" : int1.ToString(),
string_list[0]));
break;
}
return part_string;
}
} // end class PartClass
// deferred actions from 'TargetAction' actions in a triggered rule
class TargetActions
{
public string target;
public List actions;
public TargetActions(string t)
{
actions = new List();
target = t;
}
}
// here's how the rules are stored in ProconRulz (trigger, list of parts)
class ParsedRule
{
public ParsedRule()
{
parts = new List();
comment = false;
trigger = TriggerEnum.Spawn;
}
public int id; // rule identifier 1..n
public List parts; // e.g. [Not Kit Recon 2]
public TriggerEnum trigger; // trigger rule e.g. on spawn or kill
public string unparsed_rule; // the original string parsed into this rule
public bool comment; // this 'rule' is a comment to ignore at runtime
}
#endregion
#region PlayerList class
class PlayerData
{
public bool updated; // set to true during admin.listPlayers processing
public string name;
public string squad;
public string team;
public string ip;
public string ea_guid;
public string pb_guid;
public string clan;
public int ping;
public int score;
public string country_key;
public string country_name;
public PlayerData()
{
updated = false;
squad = "-1";
team = "-1";
//ip = "no IP";
//ea_guid = "No_EA_GUID";
//pb_guid = "No_PB_GUID";
clan = "No clan";
ping = -1;
score = 0;
country_key = ""; // country code
country_name = ""; // country
}
}
// this plugin maintains its own list of playernames on the server
class PlayerList
{
// player info is stored as a dictionary playername->CPlayerInfo
Dictionary info;
// when a player first joins, we cache their name/team in here (don't have a CPlayerInfo yet)
Dictionary new_player_teams;
public PlayerList()
{
// info is the main player list
info = new Dictionary();
// new_player_teams is the cache of players from OnPlayerJoin before
// they get CPlayerInfo from OnListPlayers
new_player_teams = new Dictionary();
}
public void reset()
{
info.Clear();
new_player_teams.Clear();
}
// remove player entries that don't have 'updated' true
public void scrub()
{
new_player_teams.Clear();
List scrub_keys = new List();
foreach (string player_name in info.Keys)
if (info[player_name].updated == false) scrub_keys.Add(player_name);
foreach (string player_name in scrub_keys) info.Remove(player_name);
}
public void pre_scrub()
{
foreach (string player_name in info.Keys) info[player_name].updated = false;
}
public void remove(string player_name)
{
// remove from main list
info.Remove(player_name);
//and remove from new player cache if necessary
new_player_teams.Remove(player_name);
}
// called by OnPlayerJoin
// a new player has a name and maybe a team, that's all. No CPlayerInfo
public void new_player(string player_name)
{
if (info.ContainsKey(player_name)) return; // already in main player list
if (new_player_teams.ContainsKey(player_name)) return; // already in new player cache
// create entry for the player, but put them in team -1 (i.e. they're not in a team yet)
new_player_teams.Add(player_name, "-1");
}
// here's where we add a player to the main list
// and remove them from the new player cache if necessary
public void update(CPlayerInfo inf)
{
string player_name = inf.SoldierName;
// remove from new player cache
new_player_teams.Remove(player_name);
// add to main player list (update existing entry if necessary)
if (!info.ContainsKey(player_name) || info[player_name] == null)
info[player_name] = new PlayerData();
info[player_name].name = player_name;
info[player_name].squad = inf.SquadID.ToString();
info[player_name].team = inf.TeamID.ToString();
info[player_name].ea_guid = inf.GUID;
info[player_name].clan = inf.ClanTag;
info[player_name].score = inf.Score;
info[player_name].updated = true;
}
// update based on Punkbuster info
public void update(CPunkbusterInfo inf)
{
string player_name = inf.SoldierName;
if (!info.ContainsKey(player_name) || info[player_name] == null)
info[player_name] = new PlayerData();
info[player_name].name = player_name;
info[player_name].pb_guid = inf.GUID;
info[player_name].ip = inf.Ip;
info[player_name].country_key = inf.PlayerCountryCode;
info[player_name].country_name = inf.PlayerCountry;
}
public void team_move(string player_name, string team_id, string squad_id)
{
// attempt 1: update player entry in main list
if (info.ContainsKey(player_name))
{
info[player_name].team = team_id;
info[player_name].squad = squad_id;
}
// attempt 2: maybe they've just joined - update player entry in new player list
if (new_player_teams.ContainsKey(player_name))
new_player_teams[player_name] = team_id;
return;
}
// return the current team_id of player
public string team_id(string player_name)
{
if (player_name == null) return "-1";
// attempt #1: try main player list
if (info.ContainsKey(player_name)) return info[player_name].team;
// attempt #2: try cache of new players
if (new_player_teams.ContainsKey(player_name)) return new_player_teams[player_name];
else return "-1";
}
// return the current squad_id of player
public string squad_id(string player_name)
{
if (player_name == null) return "-1";
// attempt #1: try main player list
if (info.ContainsKey(player_name)) return info[player_name].squad;
return "-1";
}
// return the number of players in team
public int teamsize(string team_id)
{
return list_players(team_id).Count;
}
// return current ping for this player, as updated in the latest admin.listPlayers
public int ping(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].ping == null) return -1;
return info[player_name].ping;
}
// return current score for this player, as updated in the latest admin.listPlayers
public int score(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].score == null) return -1;
return info[player_name].score;
}
// return EA GUID for this player, as updated in the latest admin.listPlayers
public string ea_guid(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].ea_guid == null) return "";
return info[player_name].ea_guid;
}
// return Punkbuster GUID for this player, as updated in the latest OnPunkbusterInfo
public string pb_guid(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].pb_guid == null) return "";
return info[player_name].pb_guid;
}
// return IP address for this player, as updated in the latest OnPunkbusterInfo
public string ip(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].ip == null) return "";
return info[player_name].ip;
}
// return country name for this player, as updated in the latest OnPunkbusterInfo
public string cname(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].country_name == null) return "";
return info[player_name].country_name;
}
// return country KEY for this player, as updated in the latest OnPunkbusterInfo
public string ckey(string player_name)
{
if (player_name == null ||
player_name == "" ||
!info.ContainsKey(player_name) ||
info[player_name].country_key == null) return "";
return info[player_name].country_key;
}
public List list_new_players()
{
return new List(new_player_teams.Keys);
}
public List list_players()
{
return new List(info.Keys);
}
public List list_players(string team_id)
{
List player_list = new List();
foreach (string player_name in info.Keys)
if (info[player_name].team == team_id) player_list.Add(player_name);
// also check new players
foreach (string player_name in new_player_teams.Keys)
if (new_player_teams[player_name] == team_id) player_list.Add(player_name);
return player_list;
}
public int min_teamsize()
{
int size1 = teamsize("1");
int size2 = teamsize("2");
return size1 < size2 ? size1 : size2;
}
// return a list os all the team_ids found in the player list
public List list_team_ids()
{
List team_ids = new List();
foreach (string player_name in info.Keys)
try {
if (!team_ids.Contains(info[player_name].team))
team_ids.Add(info[player_name].team);
} catch {}
foreach (string player_name in new_player_teams.Keys)
try {
if (!team_ids.Contains(new_player_teams[player_name]))
team_ids.Add(new_player_teams[player_name]);
} catch { }
team_ids.Sort(); // sort ascending
return team_ids;
}
public string clan(string player_name)
{
if (player_name == null) return "No clan";
if (player_name == "") return "No clan";
try
{
if (info.ContainsKey(player_name))
{
string clan_name = info[player_name].clan;
if (clan_name == null) return "No clan";
if (clan_name.Trim() == "") return "No clan";
return clan_name;
}
}
catch { }
return "No clan";
}
}
#endregion
#region SpawnCounts class
// counts of spawned items in team_1 and team_2 - lists player names with each item
// each entry is ->- ::[,...]
// e.g. Recon -> [sleepy, grumpy, doc]
class SpawnCounts
{
Dictionary>> counts;
List watched_items;
public SpawnCounts()
{
// team_id -> (item_name) -> (List of player names)
counts = new Dictionary>>();
watched_items = new List();
}
public List list_items()
{
return watched_items;
}
// return a list of team_ids that have spawn counts recorded against them
//public List list_team_ids()
//{
// return new List(counts.Keys);
//}
// list the players spawned with an item in a given team
public List list_players(string item_name, string team_id)
{
if (!counts.ContainsKey(team_id)) return new List(); // team has no watch items
// team doesn't have this item watched:
if (!counts[team_id].ContainsKey(item_name)) return new List();
return counts[team_id][item_name]; // return list of player names
}
// this is a bit subtle - the way we 'watch' items (kits, weapons, specs, damagetypes) is by
// creating a dictionary key entry for the string name of that item (e.g. "recon").
// The 'value' of that dictionary entry is the list of string playernames that have spawned
// with that item.
// This enables us to count the number of players with this item
// (i.e. the length of the player
// name list in that dictionary entry.).
public void watch(List items_mixed)
{
foreach (string item_mixed in items_mixed)
if (!watched_items.Contains(item_mixed.ToLower())) watched_items.Add(item_mixed.ToLower());
}
// scrub player "name" from both spawn_counts (on spawn, so we can add a fresh entry)
public void zero_player(string player_name)
{
if (player_name == null) return;
if (player_name == "") return;
foreach (string team_id in counts.Keys)
{
foreach (string item in counts[team_id].Keys)
{
counts[team_id][item].Remove(player_name);
}
}
}
// keep watch list but zero all counts (called at round end)
public void zero()
{
counts.Clear();
}
// reset to startup status
public void reset()
{
counts.Clear();
watched_items.Clear();
}
// player "name" has just spawned with item 'item', so add to counts for that team
public void add(string item_mixed, string team_id, string player_name)
{
if (player_name == null) return;
if (player_name == "") return;
string item_lcase = item_mixed.ToLower();
if (!watched_items.Contains(item_lcase)) return; // ignore if item is not 'watched'
if (!counts.ContainsKey(team_id)) // if no entry for team then create one
counts[team_id] = new Dictionary>();
if (!counts[team_id].ContainsKey(item_lcase)) // if no entry for item then create one
counts[team_id][item_lcase] = new List();
// add team/item/player_name if it's not already there
if (!counts[team_id][item_lcase].Contains(player_name))
counts[team_id][item_lcase].Add(player_name);
}
// how many players on team currently have any 'item' on list
public int count(List items_mixed, string team_id)
{
if (!counts.ContainsKey(team_id)) return 0; // if team has no watched items then 0
List items_lcase = new List();
if (items_mixed != null)
foreach (string i in items_mixed) items_lcase.Add(i.ToLower());
List player_list = new List(); // list of players with these items
foreach (string item_lcase in items_lcase)
{
if (!watched_items.Contains(item_lcase)) continue; // if not watched then 0
// if team does not have this item then 0
if (!counts[team_id].ContainsKey(item_lcase)) continue;
// return length of player name list for this team/item
player_list.AddRange(counts[team_id][item_lcase]);
}
return player_list.Count;
}
// return 'true' if player has any item on the list
public bool has_item(List items_mixed, string player_name)
{
if (player_name == null) return false;
if (player_name == "") return false;
List items_lcase = new List();
if (items_mixed != null)
foreach (string i in items_mixed) items_lcase.Add(i.ToLower());
foreach (string item_lcase in items_lcase)
{
if (!watched_items.Contains(item_lcase)) continue;// if not watched then 0
foreach (string team_id in counts.Keys)
if (counts[team_id].ContainsKey(item_lcase) &&
counts[team_id][item_lcase].Contains(player_name)) return true;
}
return false;
}
}
#endregion
#region VarsClass class - this manages the rulz variables (Set, Incr, Decr, If)
// stores values of rulz variables
class VarsClass
{
// basic proconrulz vars
Dictionary vars;
// persistent vars - stored in Configs/proconrulz.ini
Dictionary> ini_vars;
string ini_filename;
public VarsClass(string file_hostname_port)
{
ini_filename = "Configs" + Path.DirectorySeparatorChar + file_hostname_port + "_proconrulz.ini";
// as of ver 37 all vars stored as strings
vars = new Dictionary();
ini_vars = new Dictionary>();
}
// keep list but zero all values (called at round end)
public void zero()
{
vars.Clear();
}
// reset to startup status
public void reset()
{
vars.Clear();
ini_vars.Clear();
ini_load(ini_filename);
}
// MANGLE is a fairly important function in ProconRulz vars processing...
// all vars are converted to 'server' variables but the usage in rulz allows
// them to appear as 'per-player' variables.
// i.e. %streak% in a rule is a UNIQUE variable for each player
// the rulz processing converts this to %server_streak[]% as the unique name
// so in effect, %streak% is shorthand for %server_streak[%p%]%
//
// We convert the var name into something valid globally
// i.e. %kills% -> %server_kills[]%
// %squad_kills% -> %server_squad_kills[1][2]% (where 1 = team, 2 = squad id's)
// %team_kills% -> %server_team_kills[1]% (where "1" is team id for player)
// %server_kills% -> unchanged
private string mangle(string player_name, string var_name)
{
var_name = var_name.Replace("$", "%");
// check for a valid var name %...%
if (var_name == null ||
var_name.Length < 3 ||
!var_name.StartsWith("%") ||
!var_name.EndsWith("%")) return null;
// replace [%vars%] in this var name with their value
var_name = replace_index_vars(player_name, var_name);
// if it's a 'server variable' return it unchanged
if (var_name.ToLower().StartsWith("%server_")) return var_name;
// ini var name - return unchanged
if (var_name.ToLower() == "%ini%") return var_name;
if (var_name.ToLower().StartsWith("%ini_")) return var_name;
// see if it's a team, squad or a player variable which means we much have a valid player name
if (player_name == null) return null;
// raw_name is a var name without the % %
string raw_name = var_name.Substring(1, var_name.Length - 2);
if (var_name.ToLower().StartsWith("%team_"))
// e.g. %team_kills% -> %server_team_kills[1]% (where "1" is team id for player)
return "%server_" + raw_name + "[" + players.team_id(player_name).ToString() + "]%";
if (var_name.ToLower().StartsWith("%squad_"))
// e.g. %squad_kills% -> %server_squad_kills[1][2]% (where 1 = team, 2 = squad id's)
return "%server_" + raw_name + "[" + players.team_id(player_name).ToString() + "][" +
players.squad_id(player_name).ToString() + "]%";
// proc has been called with a 'player variable',
// e.g. "%streak%", mangle to "server_streak[playername]%"
return "%server_" + raw_name + "[" + player_name + "]%";
}
// value of an expression : replace %v%-type subst values, then replace %streak%-type rulz vars:
private string get_value(string player_name, string input_exp, Dictionary keywords)
{
// replace substitution variables, e.g. %p% with playername from keywords
string substituted_exp = replace_keys(input_exp, keywords);
// now exp doesn't contain any substitution vars
// replace user vars with their values, e.g. %server_kills% or whatever user has used in rulz
string replaced_exp = replace_vars(player_name, substituted_exp);
// now exp has no vars, but may include arithmetic
string reduced_exp = reduce(replaced_exp); // "1+1" -> "2"
return reduced_exp;
}
// convert %ini__% to Array[0..2] [ini,,]
string[] var_to_ini(string full_var_name)
{
string[] ini_parts = new string[3];
if (full_var_name == "%ini%")
{
ini_parts[0] ="ini";
}
else if (full_var_name.StartsWith("%ini_"))
{
ini_parts[0] = "ini";
// e.g. %ini_vars_plugin_settings%
string[] var_parts = full_var_name.Split('_');
if (var_parts.Length >= 2)
{
if (var_parts.Length == 2) // no section name, e.g. %ini_myvar% so default to [vars]
{
ini_parts[1] = var_parts[1].TrimEnd(new char[] { '%' }); // var name
}
else
{
ini_parts[1] = var_parts[1]; // section name
ini_parts[2] = full_var_name.Substring(5 + var_parts[1].Length + 1).TrimEnd(new char[] { '%' });
}
}
}
return ini_parts;
}
// find the value of an 'atom' i.e. a string, int or variable
// exp can be a variable name or an integer or a string
private string atom_value(string player_name, string exp)
{
if (exp == null || exp == "") return "";
// try to get a number
float i;
try
{
i = float.Parse(exp);
return i.ToString();
}
catch { }
// ok, we didn't get an int, so try a variable lookup
// if not a %..% var just return the string
if (!exp.StartsWith("%") || !exp.EndsWith("%")) return exp;
string full_var_name = mangle(player_name, exp);
if (full_var_name == null) return "";
// return the variable value if there is one
if (vars.ContainsKey(full_var_name))
return vars[full_var_name];
else // we'll try the ini values
{
//convert %ini__% into list [section_name, var_name]
string[] ini_parts = var_to_ini(full_var_name);
if (ini_parts[0] != null)
{
string section_name = ((ini_parts[1] == null) ? "vars" : ini_parts[1]);
string var_name = ini_parts[2];
if ( ini_vars.ContainsKey(section_name) &&
var_name != null &&
ini_vars[section_name].ContainsKey(var_name))
{
return ini_vars[section_name][var_name];
}
}
}
// this *is* a %..% var but we didn't get a value, so we'll return "0"
return "0";
}
// SET the value of a variable
// if playername is null, var must be %server_..%
// keywords can be null (so no keyword substitutions)
public void set_value(string player_name, string var_name, string assign_value, Dictionary keywords)
{
// substitute the %mm% type subst vars
var_name = keywords == null ? var_name : replace_keys(var_name, keywords);
string full_var_name = mangle(player_name, var_name);
if (full_var_name == null) return;
// now get result of assign_value
string result = get_value(player_name, assign_value, keywords);
// see if this var has a 'rounding' attribute, e.g. %xxx.2%
int var_index = var_name.LastIndexOf('.');
if (var_index > 0)
{
// ok so it IS a 'rounding var'
// so check if the result has a '.' in it...
int result_index = result.LastIndexOf('.');
if (result_index >= 0)
{
// so now we're looking at "Set %x.2% 1.23456"
try
{
// decimals is the number in the var name after the '.'
int decimals = Int32.Parse(var_name.Substring(var_index + 1, var_name.Length - var_index - 2));
// add/subtract 0.005 (or similar) so truncate = rounding
double result_float = double.Parse(result);
double adjust = 0.5 / Math.Pow(10, decimals); // here is rounding adjust value
if (result_float >= 0)
{
result_float = result_float + adjust;
}
else
{
result_float = result_float - adjust;
}
result = result_float.ToString();
result_index = result.LastIndexOf('.'); // did '.' move ?
if (decimals == 0)
{
// for %x.0% remove the '.' as well as the decimal digits...
result = result.Substring(0, result_index);
}
else
{
// here's where we update 1.23456 to 1.23
result = result.Substring(0, result_index + decimals + 1);
// an exception could occur above if the string is too short, so result is unchanged
}
}
catch
{
}
}
}
// try and get ini var parts from full_var_name
string[] ini_parts = var_to_ini(full_var_name);
if (ini_parts[0] == null)
{
// didn't parse an ini section_name/var_name so do simple vars assign
vars[full_var_name] = result;
}
else
{
// successfully parsed %ini_section_var% so assign and update ini file
string ini_section_name = ini_parts[1];
string ini_var_name = ini_parts[2];
// load entire file...
ini_load(ini_filename);
// update our local value ini_vars
ini_set_value(ini_section_name, ini_var_name, result);
// write all ini_vars out to proconrulz.ini file
ini_save(ini_filename);
}
return;
}
// INCREMENT THE VALUE OF A VARIABLE
public void incr(string player_name, string var_name, Dictionary keywords)
{
int result = 1;
try
{
result = Int32.Parse(get_value(player_name, var_name, keywords)) + 1;
}
catch { }
set_value(player_name, var_name, result.ToString(), keywords);
return;
}
// DECREMENT THE VALUE OF A VARIABLE
public void decr(string player_name, string var_name, Dictionary keywords)
{
int result = 0;
try
{
result = Int32.Parse(get_value(player_name, var_name, keywords)) - 1;
if (result < 0) result = 0;
}
catch { }
set_value(player_name, var_name, result.ToString(), keywords);
return;
}
public bool test(string player_name, List arguments, Dictionary keywords)
{
bool condition_greenbig = false;
for (int e = 0; e < arguments.Count; e += 4) {
bool condition_green;
string logical = null;
try { logical = arguments[e - 1]; } catch (Exception F) { }
string val_i = arguments[e];
string cond = arguments[e+1];
string val_j = arguments[e+2];
string i = get_value(player_name, val_i, keywords).ToLower();
string j = get_value(player_name, val_j, keywords).ToLower();
switch (cond.ToLower())
{
case "=": condition_green = i == j; break;
case "==": condition_green = i == j; break;
case "!=": condition_green = i != j; break;
case "<>": condition_green = i != j; break;
case ">": condition_green = bigger(i, j); break; // i > j
case "<": condition_green = (i != j) && !bigger(i, j); break; // i < j
case "=>": condition_green = (i == j) || bigger(i, j); break; // i >= j
case ">=": condition_green = (i == j) || bigger(i, j); break; // i >= j
case "<=": condition_green = !bigger(i, j); break; // i <= j;
case "=<": condition_green = !bigger(i, j); break; // i <= j;
case "contains": condition_green = i.Contains(j); break; // i contains j
case "regx": condition_green = Regex.IsMatch(i, @"" + j); break; // regex patterns. Value 2 is the pattern.
case "word": condition_green = Regex.IsMatch(i, string.Format(@"\b{0}\b", j), System.Text.RegularExpressions.RegexOptions.IgnoreCase); break;
default: condition_green = false; break;
}
if(!String.IsNullOrEmpty(logical))
{
switch (logical)
{
case "AND": if (condition_greenbig && condition_green) { condition_greenbig = true; } else { condition_greenbig = false; }; break;
case "OR": if (condition_greenbig || condition_green) { return true; } else { condition_greenbig = false; }; break;
case "~AND": if (condition_greenbig && !condition_green) { condition_greenbig = true; } else { condition_greenbig = false; }; break;
case "~OR": if (condition_greenbig || !condition_green) { return true; } else { condition_greenbig = false; }; break;
}
} else
{
condition_greenbig = condition_green;
}
}
return condition_greenbig;
}
//
private bool bigger(string x, string y)
{
float i = 0, j = 0;
try
{
i = float.Parse(x);
j = float.Parse(y);
return i > j;
}
catch { }
return String.Compare(x, y, true) > 0;
}
// reduce arithmetic expression to their value e.g. "2+2" -> "4"
public string reduce(string exp)
{
if (!exp.Contains("+") && !exp.Contains("-") && !exp.Contains("*") && !exp.Contains("/"))
{
return exp;
}
string left, right, left_result, right_result;
float left_float = 0, right_float = 0;
bool left_num = false;
bool right_num = false;
foreach (char op in new char[4] { '+', '-', '*', '/' })
{
int i = exp.IndexOf(op);
if (i > 0)
{
left = exp.Substring(0, i);
right = exp.Substring(i + 1, exp.Length - i - 1);
left_result = reduce(left);
right_result = reduce(right);
try
{
left_float = float.Parse(left_result);
left_num = true;
}
catch { };
try
{
right_float = float.Parse(right_result);
right_num = true;
}
catch { };
if (left_num && right_num)
{
switch (op)
{
case '+':
return (left_float + right_float).ToString();
case '-':
return (left_float - right_float).ToString();
case '*':
return (left_float * right_float).ToString();
case '/':
if (right_float == 0) return "0";
return (left_float / right_float).ToString();
}
}
if (left_num)
{
return left_float.ToString() + op + right_result;
}
if (right_num)
{
return left_result + op + right_float.ToString();
}
return left_result + op + right_result;
}
}
return exp;
}
// replace %var_name% in message with the var value
// note it could be a var name using subst variables %server_%p%_deaths%
// message already has subst vars replaced (e.g. %p%)
public string replace_vars(string player_name, string message)
{
message = message.Replace("$", "%");
// e.g. message is "player K/D is %kills%/%deaths%"
if (message == null) return null;
if (message.Length < 3) return message;
// first replace rulz vars inside [ brackets e.g. %kills[%killer%]% -> %kills[bambam]%
message = replace_index_vars(player_name, message);
int j = 0;
int fragment_start = 0;
string vars_message = "";
while (fragment_start < message.Length)
{
int i = message.IndexOf("%", fragment_start);
j = -1; // second % not found - update if we find first %
if (i >= 0 && message.Length - i > 2) j = message.IndexOf("%", i + 2);
// now i is index of 1st %, and j the 2nd
// add the non-var fragment
if (i < 0 || j < 0) // didn't find the first % or second %
{
vars_message += message.Substring(fragment_start);
break;
}
// add the non-var piece
vars_message += message.Substring(fragment_start, i - fragment_start);
// add the var subsitution
vars_message += atom_value(player_name, message.Substring(i, j - i + 1));
fragment_start = j + 1;
}
return vars_message;
}
// here we replace the %...[%...%]% vars between []
// e.g. %server_kills[%server_killername%]% -> %server_kills[bambam]%
// note that the user could wrap a non-nested var in [] in a message, e.g. [%x%]
public string replace_index_vars(string player_name, string message)
{
if (message == null) return null;
if (message.Length < 8) return message; // "%x[%y%]%" is min length for any nested var
int j = 0;
int fragment_start = 0;
string vars_message = "";
bool in_var = false; // toggle to keep track of whether we're inside a %..% var or not
while (fragment_start < message.Length)
{
// find the start of the non-nested var
int i = message.IndexOf("%", fragment_start);
if (i < 0) // didn't find the first %
{
vars_message += message.Substring(fragment_start);
break;
}
// found a '%'
if (!in_var)
{
in_var = true;
// i is pointing to the second % of a non-nested var
// add the non-var piece up to and including the second %
vars_message += message.Substring(fragment_start, i - fragment_start + 1);
// copy fragment up to here and continue
fragment_start = i + 1;
continue;
}
// in_var = true
if (message[i - 1] != '[')
{
// in a var, but this is not [% opening a nested var
in_var = false;
// i is pointing to the second % of a non-nested var
// add the non-var piece up to and including the second %
vars_message += message.Substring(fragment_start, i - fragment_start + 1);
// copy fragment up to here and continue
fragment_start = i + 1;
continue;
}
// now i points to % in [% at start of nested var
// so HERE we have start of a nested var with % at i after [
j = -1; // now find %]
if (message.Length - i > 3) j = message.IndexOf("%]", i + 2);
// now i is index of 1st %, and j the index of %]
// add the non-var fragment
if (j < 0) // didn't find the %]
{
vars_message += message.Substring(fragment_start);
break;
}
// add the non-var piece up to and including the [
vars_message += message.Substring(fragment_start, i - fragment_start);
// add the var subsitution
vars_message += atom_value(player_name, message.Substring(i, j - i + 1));
// now add the closing ]
vars_message += "]";
fragment_start = j + 2;
}
return vars_message;
}
// return all the vars - called on "prdebug dump"
public Dictionary dump() { return vars; }
/* Read/Write .ini Files
///
/// Version 1, 2009-08-15
/// http://www.Stum.de
/// It supports the simple .INI Format:
///
/// [SectionName]
/// Key1=Value1
/// Key2=Value2
///
/// [Section2]
/// Key3=Value3
///
/// You can have empty lines (they are ignored), but comments are not supported
/// Key4=Value4 ; This is supposed to be a comment, but will be part of Value4
///
/// Whitespace is not trimmed from the beginning and end of either Key and Value
///
/// Licensed under WTFPL
/// http://sam.zoy.org/wtfpl/
*/
private readonly Regex _sectionRegex = new Regex(@"^\[(?[^\]]+)(?=\])");
private readonly Regex _keyValueRegex = new Regex(@"(?[^=]+)=(?.+)");
/// Get a specific value from the .ini file
/// The value of the given key in the given section, or NULL if not found
public string ini_get_value(string sectionName, string key)
{
if (ini_vars.ContainsKey(sectionName) && ini_vars[sectionName].ContainsKey(key))
return ini_vars[sectionName][key];
else
return null;
}
/// Set a specific value in a section
public void ini_set_value(string sectionName, string key, string value)
{
// remove entry if value is 0
if (value == "0")
{
if (sectionName != null)
{
if (ini_vars.ContainsKey(sectionName))
{
if (key != null)
{
//remove this variable
ini_vars[sectionName].Remove(key);
// if section is now empty, remove that
if (ini_vars[sectionName].Count == 0)
{
ini_vars.Remove(sectionName);
}
}
else
{
// sectionName not null, key is null, so remove section
ini_vars.Remove(sectionName);
}
}
}
else
{
// sectionName is null, so reset ALL variables
ini_vars.Clear();
}
}
else
{
if (!ini_vars.ContainsKey(sectionName)) ini_vars[sectionName] = new Dictionary();
ini_vars[sectionName][key] = value;
}
}
/// Get all the Values for a section
public Dictionary ini_get_section(string sectionName)
{
if (ini_vars.ContainsKey(sectionName))
return new Dictionary(ini_vars[sectionName]);
else
return new Dictionary();
}
/// Set an entire sections values
public void ini_set_section(string sectionName, IDictionary sectionValues)
{
if (sectionValues == null) return;
ini_vars[sectionName] = new Dictionary(sectionValues);
}
/// Load an .INI File
public bool ini_load(string filename)
{
if (File.Exists(filename))
{
try
{
string[] content;
content = File.ReadAllLines(filename);
ini_vars = new Dictionary>();
string currentSectionName = string.Empty;
foreach (string line in content)
{
Match m = _sectionRegex.Match(line.Trim());
if (m.Success)
{
currentSectionName = m.Groups["SectionName"].Value;
}
else
{
m = _keyValueRegex.Match(line);
if (m.Success)
{
string key = m.Groups["Key"].Value.Trim();
string value = m.Groups["Value"].Value.Trim();
Dictionary kvpList;
if (ini_vars.ContainsKey(currentSectionName))
{
kvpList = ini_vars[currentSectionName];
}
else
{
kvpList = new Dictionary();
}
kvpList[key] = value;
ini_vars[currentSectionName] = kvpList;
}
}
}
return true;
}
catch
{
return false;
}
}
else
{
return false;
}
}
/// Save the content of this class to an INI File
public bool ini_save(string filename)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
if (ini_vars != null)
{
foreach (string sectionName in ini_vars.Keys)
{
sb.AppendFormat("[{0}]\r\n", sectionName);
foreach (string keyValue in ini_vars[sectionName].Keys)
{
sb.AppendFormat("{0}={1}\r\n", keyValue, ini_vars[sectionName][keyValue]);
}
}
}
try
{
File.WriteAllText(filename, sb.ToString());
return true;
} catch
{
return false;
}
}
}
#endregion
#region Global Vars
#region server_ip.cfg variables
//**********************************************************************************************
//**********************************************************************************************
// VARIABLES USED IN server_ip.cfg
//**********************************************************************************************
//**********************************************************************************************
// game type (for loading rulz files)
GameIdEnum game_id = GameIdEnum.BF4;
int yell_delay = 5; // how long to have the yell message on screen
int kill_delay = 5000; // default (milliseconds) delay if the *rule* does not specify a delay
// between the event and the player being killed
int ban_delay = 60 * 60 * 24 * 7; // default temp ban is for a week in secs (not server_ip.cfg)
// If 'yes' then players on procon 'Reserved'
// list have immunity from kick/kill
ProtectEnum protect_players = ProtectEnum.Admins;
// installing admin has to check the "Rules of Conduct" checkbox
enumBoolYesNo roc_read = enumBoolYesNo.No;
ReserveItemEnum reservationMode = ReserveItemEnum.Player_reserves_item_until_respawn;
// a flag if yes will output debug console writes
static enumBoolYesNo trace_rules = enumBoolYesNo.No;
// this list stores the rules read from server_ip.cfg and user rulz files
//static Dictionary> unparsed_rulz = new Dictionary>();
static List unparsed_rules = new List();
// list of rulz.txt filenames to load
static List rulz_filenames = new List();
List whitelist_players = new List(); // list of playernames to be protected
List whitelist_clans = new List(); // list of clans to be protected
LogFileEnum log_file = LogFileEnum.PluginConsole;
#endregion
//**********************************************************************************************
//**********************************************************************************************
// GLOBAL VARIABLES
//**********************************************************************************************
//**********************************************************************************************
string auth_name = "bambam_ofc & Kontrabandito"; // Bambam, author of this plugin (see forum.myrcon.com)
bool plugin_enabled; // set true and false by Procon, when admin enables/disables this plugin
WeaponDictionary weaponDefines; //
SpecializationDictionary specDefines; //
static PlayerList players = new PlayerList();
// record GUIDs of players
Dictionary player_info = new Dictionary();
// lists of players spawned with watched items
SpawnCounts spawn_counts = new SpawnCounts();
// kill_counts accumulates the item counts on KILL for each player
// e.g. kill_counts["bambam"]["AUG"] = 3 means bambam has 3 AUG kills.
// count added with add_kill_count(player_name, item_name)
// count retrieved with count_kill_item(player_name, item_name)
Dictionary> kill_counts
= new Dictionary>();
// the count of number of times each player has triggered each rule
// i.e. player_name ->
// reset at round start
Dictionary> rule_counts
= new Dictionary>();
// timestamps of players triggering rules, for 'Rate' calculations
// i.e. player_name ->
// reset when plugin loaded. Meanwhile players are individually
// 'scrubbed' out of the timestamp lists
Dictionary> rule_times
= new Dictionary>();
// player_blocks will block a player with a given item, triggering PlayerBlock rule
Dictionary> player_blocks = new Dictionary>();
// Dictionary playername->kit_key of the Kit the player spawned with
Dictionary player_kit = new Dictionary();
// whitelist of players not to kick or kill
List reserved_slot_players = new List();
// list of unparsed rules from user rulz files
Dictionary filez_rulz = new Dictionary();
// list of parsed rules loaded from user
List parsed_rules = new List();
CMap current_map; // map info of current loaded map, set by OnLoadingLevel()
string current_map_mode = "None"; // mod for BF3, cannot derive map mode from map name
int rulz_spam_limit = 2; // max # times a player can request RULZ/MOTD
bool rulz_enable = true;
// WriteDebugInfo will increment this - Procon bug may duplicate lines in log?
int debug_write_count = 0;
char rulz_key_separator = '&'; // char used to replace ' ' in rulz
static char rulz_item_separator = ','; // separator for items in a condition, e.g. Weapon SMAW,RPG-7
// BF4 only trigger OnRoundOver when ALL 3 events have completed (OnRoundOver/Players/TeamScores)
int round_over_event_count = 0; // zeroed in OnLevelLoaded, incr in RoundOver events
// object to hold runtime rulz variables
VarsClass rulz_vars = null;
ParsedRule rule_prefix; // used in parse_rules() as default first parts if rule has no trigger
// at start of parse initialized to TriggerEnum.Spawn
#endregion
#region Populate dictionary of %% substitution keys and startup default rules
public ProconRulz()
{
subst_keys.Add(SubstEnum.Player, new List());
subst_keys[SubstEnum.Player].Add("%p%");
subst_keys[SubstEnum.Player].Add("$p$");
subst_keys.Add(SubstEnum.Victim, new List());
subst_keys[SubstEnum.Victim].Add("%v%");
subst_keys.Add(SubstEnum.Weapon, new List());
subst_keys[SubstEnum.Weapon].Add("%w%");
subst_keys.Add(SubstEnum.WeaponKey, new List());
subst_keys[SubstEnum.WeaponKey].Add("%wk%");
subst_keys.Add(SubstEnum.Damage, new List());
subst_keys[SubstEnum.Damage].Add("%d%");
subst_keys.Add(SubstEnum.DamageKey, new List());
subst_keys[SubstEnum.DamageKey].Add("%dk%");
subst_keys.Add(SubstEnum.Kit, new List());
subst_keys[SubstEnum.Kit].Add("%k%");
subst_keys.Add(SubstEnum.VictimKit, new List());
subst_keys[SubstEnum.VictimKit].Add("%vk%");
subst_keys.Add(SubstEnum.KitKey, new List());
subst_keys[SubstEnum.KitKey].Add("%kk%");
subst_keys.Add(SubstEnum.Spec, new List()); // available On Spawn only
subst_keys[SubstEnum.Spec].Add("%spec%"); // available On Spawn only
subst_keys.Add(SubstEnum.SpecKey, new List()); // available On Spawn only
subst_keys[SubstEnum.SpecKey].Add("%speck%"); // available On Spawn only
subst_keys.Add(SubstEnum.Count, new List());
subst_keys[SubstEnum.Count].Add("%c%");
subst_keys.Add(SubstEnum.TeamCount, new List());
subst_keys[SubstEnum.TeamCount].Add("%tc%");
subst_keys.Add(SubstEnum.ServerCount, new List());
subst_keys[SubstEnum.ServerCount].Add("%sc%");
subst_keys.Add(SubstEnum.PlayerTeam, new List());
subst_keys[SubstEnum.PlayerTeam].Add("%pt%");
subst_keys.Add(SubstEnum.PlayerSquad, new List());
subst_keys[SubstEnum.PlayerSquad].Add("%ps%");
subst_keys.Add(SubstEnum.PlayerTeamKey, new List());
subst_keys[SubstEnum.PlayerTeamKey].Add("%ptk%");
subst_keys.Add(SubstEnum.VictimTeamKey, new List());
subst_keys[SubstEnum.VictimTeamKey].Add("%vtk%");
subst_keys.Add(SubstEnum.PlayerSquadKey, new List());
subst_keys[SubstEnum.PlayerSquadKey].Add("%psk%");
subst_keys.Add(SubstEnum.VictimTeam, new List());
subst_keys[SubstEnum.VictimTeam].Add("%vt%");
subst_keys.Add(SubstEnum.Range, new List());
subst_keys[SubstEnum.Range].Add("%r%");
subst_keys.Add(SubstEnum.BlockedItem, new List());
subst_keys[SubstEnum.BlockedItem].Add("%b%");
subst_keys.Add(SubstEnum.Teamsize, new List());
subst_keys[SubstEnum.Teamsize].Add("%n%");
subst_keys.Add(SubstEnum.Teamsize1, new List());
subst_keys[SubstEnum.Teamsize1].Add("%ts1%");
subst_keys.Add(SubstEnum.Teamsize2, new List());
subst_keys[SubstEnum.Teamsize2].Add("%ts2%");
subst_keys.Add(SubstEnum.PlayerTeamsize, new List());
subst_keys[SubstEnum.PlayerTeamsize].Add("%pts%");
subst_keys.Add(SubstEnum.Headshot, new List());
subst_keys[SubstEnum.Headshot].Add("%h%");
subst_keys.Add(SubstEnum.Map, new List());
subst_keys[SubstEnum.Map].Add("%m%");
subst_keys.Add(SubstEnum.MapMode, new List());
subst_keys[SubstEnum.MapMode].Add("%mm%");
subst_keys.Add(SubstEnum.Target, new List()); // e.g. playername from say text
subst_keys[SubstEnum.Target].Add("%t%"); // e.g. playername from say text
subst_keys.Add(SubstEnum.Text, new List()); // e.g. say text
subst_keys[SubstEnum.Text].Add("%text%"); // e.g. say/serversay text
subst_keys.Add(SubstEnum.Channel, new List()); // e.g. say channel
subst_keys[SubstEnum.Channel].Add("%textchannel%"); // e.g. say channel
subst_keys.Add(SubstEnum.TargetText, new List()); // used only by TargetPlayer
subst_keys[SubstEnum.TargetText].Add("%targettext%"); // used only by TargetPlayer
subst_keys.Add(SubstEnum.Ping, new List()); // ping in milliseconds (i.e as displayed)
subst_keys[SubstEnum.Ping].Add("%ping%"); // ping in milliseconds (i.e as displayed)
subst_keys.Add(SubstEnum.EA_GUID, new List()); // from admin.listPlayers
subst_keys[SubstEnum.EA_GUID].Add("%ea_guid%"); // from admin.listPlayers
subst_keys.Add(SubstEnum.PB_GUID, new List()); // from OnPunkbusterPlayerInfo
subst_keys[SubstEnum.PB_GUID].Add("%pb_guid%"); // from OnPunkbusterPlayerInfo
subst_keys.Add(SubstEnum.IP, new List()); // IP address
subst_keys[SubstEnum.IP].Add("%ip%"); // IP address
subst_keys.Add(SubstEnum.PlayerCountry, new List()); // from pb info
subst_keys[SubstEnum.PlayerCountry].Add("%pcountry%"); // from pb info
subst_keys.Add(SubstEnum.PlayerCountryKey, new List()); // from pb info
subst_keys[SubstEnum.PlayerCountryKey].Add("%pcountrykey%"); // from pb info
subst_keys.Add(SubstEnum.VictimCountry, new List()); // from pb info
subst_keys[SubstEnum.VictimCountry].Add("%vcountry%"); // from pb info
subst_keys.Add(SubstEnum.VictimCountryKey, new List()); // from pb info
subst_keys[SubstEnum.VictimCountryKey].Add("%vcountrykey%"); // from pb info
subst_keys.Add(SubstEnum.Random, new List());
subst_keys[SubstEnum.Random].Add("%random%");
subst_keys.Add(SubstEnum.Hhmmss, new List()); // time HH:MM:SS
subst_keys[SubstEnum.Hhmmss].Add("%hms%");
subst_keys.Add(SubstEnum.Seconds, new List()); // time seconds
subst_keys[SubstEnum.Seconds].Add("%seconds%");
subst_keys.Add(SubstEnum.Date, new List()); // time seconds
subst_keys[SubstEnum.Date].Add("%ymd%");
// default rulz
unparsed_rules.Add("# JOINER/LEAVER LOG");
unparsed_rules.Add("On Join;Say ^2%p%^0 has joined the server");
unparsed_rules.Add("On Leave;Say ^2%p%^0 has left the server");
rulz_filenames.Add("proconrulz_rules.txt");
}
#endregion
#region Plugin startup routines and On Init procedure
//**********************************************************************************************
//**********************************************************************************************
// PROCON STARTUP ROUTINES
//**********************************************************************************************
//**********************************************************************************************
public string GetPluginName() { return "ProconRulz"; }
public string GetPluginVersion() { return version; }
public string GetPluginAuthor() { return auth_name; }
public string GetPluginWebsite() { return ""; }
public string GetPluginDescription() { return get_details(); }
public void OnPluginLoaded(string strHostName, string strPort, string strPRoConVersion)
{
rulz_vars = new VarsClass(String.Format("{0}_{1}", strHostName, strPort));
WriteConsole("ProconRulz loaded");
try
{
weaponDefines = GetWeaponDefines();
}
catch
{
WriteConsole("ProconRulz: ^1failed to load weapon definitions");
}
try
{
specDefines = GetSpecializationDefines();
}
catch
{
WriteConsole("ProconRulz: ^1failed to load spec definitions");
}
WriteConsole(String.Format("weaponDefines size = {0}, specDefines size = {1}",
weaponDefines.Count,
specDefines.Count));
this.RegisterEvents(this.GetType().Name,
"OnPlayerSpawned",
"OnPlayerKilled",
"OnPlayerTeamChange",
"OnPlayerSquadChange",
"OnPlayerJoin",
"OnPlayerLeft",
"OnListPlayers",
"OnReservedSlotsList",
"OnRoundOver",
"OnRoundOverPlayers",
"OnRoundOverTeamScores",
"OnLoadingLevel", // for BFBC2
"OnLevelLoaded", // for BF3
"OnCurrentLevel", // BFBC2 only
"OnGlobalChat",
"OnTeamChat",
"OnSquadChat",
"OnServerInfo",
"OnPunkbusterPlayerInfo"
);
// exec listPlayers to initialise players global
ExecuteCommand("procon.protected.send", "admin.listPlayers", "all");
}
public void OnPluginEnable()
{
plugin_enabled = true;
this.WriteConsole(String.Format("^bProconRulz: ^2plugin enabled, version {0}", version));
WriteDebugInfo(String.Format("weaponDefines size = {0}, specDefines size = {1}",
weaponDefines.Count,
specDefines.Count));
rulz_enable = true;
load_rulz_from_files();
reset_counts(); // reset the list of 'watched' items and their counts
// reset rulz runtime vars
rulz_vars.reset();
// search for user rulz file (Plugins//*rulz.txt)
//load_rulz_from_files(game_id);
parse_rules();
load_reserved_slot_players();
// exec currentLevel to initialise current_map global, so we don't have to wait for a map load
// ** NOT working in BF3 R8
ExecuteCommand("procon.protected.send", "admin.currentLevel");
}
public void OnPluginDisable()
{
plugin_enabled = false;
WriteConsole("^bProconRulz: ^1plugin disabled");
// the rest of this proc is just to output some debug info when user selects "disable plugin"
WriteDebugInfo("ProconRulz: These rules were loaded from settings:");
foreach (ParsedRule rule in parsed_rules)
{
print_parsed_rule(rule);
} // end looping through parsed_rules
WriteDebugInfo(String.Format("ProconRulz: These were the 'watched' items in the rules:"));
WriteDebugInfo(String.Format("ProconRulz: {0}",
string.Join(", ", spawn_counts.list_items().ToArray())));
}
// proconRulz On Init (on plugin load, enable, round start)
public void OnInit()
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnInit******************************");
WhatISaid.Clear();
scan_rules(TriggerEnum.Init, null,
new Dictionary(), null, null, null);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnInit");
PrintException(ex);
}
}
#endregion
#region plugin settings incl. load the rulz
//**********************************************************************************************
//**********************************************************************************************
// MANAGE THE PLUGIN SETTINGS
//**********************************************************************************************
//**********************************************************************************************
// load rulz .txt files listed in rulz_filenames
void load_rulz_from_files()
{
if (rulz_filenames.Count == 0) return;
try
{
//string folder = @"Plugins\"+game_id.ToString()+@"\";
string folder = @"Plugins"+Path.DirectorySeparatorChar+game_id.ToString()+Path.DirectorySeparatorChar;
WriteConsole("ProconRulz: Loading rulz from .txt files in " + folder);
//string[] file_paths = Directory.GetFiles(folder, "proconrulz_*.txt");
// do nothing and return if no rulz files found
//if (file_paths.Length == 0)
//{
// WriteConsole("ProconRulz: no user rulz files defined (will just use settings)");
// return;
//}
// start with an empty list of user rulz from files
filez_rulz.Clear();
foreach (string filename in rulz_filenames)
{
if (File.Exists(folder + filename))
{
WriteConsole("ProconRulz: Loading " + folder + filename);
string[] rulz = System.IO.File.ReadAllLines(folder + filename);
if (rulz.Length > 0 & !rulz[0].Contains("#disable"))
filez_rulz.Add(filename, rulz);
}
else
{
string path = Path.GetFullPath(folder + filename);
WriteConsole("ProconRulz: Skipping " + path + " NOT FOUND");
}
}
}
catch (Exception ex)
{
PrintException(ex);
}
}
// plugin variables are declared in 'Global Vars' region above
// ASSIGN values to program globals FROM server_ip.cfg and from Plugin Settings pane
public void SetPluginVariable(string strVariable, string raw_strValue)
{
try
{
string strValue = CPluginVariable.Decode(raw_strValue);
switch (strVariable)
{
case "Game":
game_id = (GameIdEnum)Enum.Parse(typeof(GameIdEnum), strValue);
break;
case "Delay before kill":
kill_delay = Int32.Parse(strValue);
break;
case "Yell seconds":
yell_delay = Int32.Parse(strValue);
break;
//case "Player keeps items on respawn":
// reservationMode = (ReserveItemEnum)Enum.Parse(typeof(ReserveItemEnum), strValue);
// break;
case "EA Rules of Conduct read and accepted":
roc_read = (enumBoolYesNo)Enum.Parse(typeof(enumBoolYesNo), strValue);
break;
case "Trace rules":
trace_rules = (enumBoolYesNo)Enum.Parse(typeof(enumBoolYesNo), strValue);
break;
case "Protect these players from Kick or Kill":
protect_players
= (ProtectEnum)Enum.Parse(typeof(ProtectEnum), strValue);
break;
case "Rules":
string strValueHtmlDecode = raw_strValue.Contains(" ") ? raw_strValue : raw_strValue.Replace("+", " ");
string strValueUnencoded;
try
{
strValueUnencoded = Uri.UnescapeDataString(strValueHtmlDecode);
}
catch
{
strValueUnencoded = strValueHtmlDecode;
}
unparsed_rules = new List(strValueUnencoded.Split(new char[] { '|' }));
rulz_vars.reset();
reset_counts();
parse_rules();
break;
case "Rulz .txt filenames":
rulz_filenames = new List(CPluginVariable.DecodeStringArray(strValue));
// look for rulz files and add those to unparsed_rules
load_rulz_from_files();
rulz_vars.reset();
reset_counts();
parse_rules();
break;
case "Player name whitelist":
whitelist_players = new List(CPluginVariable.DecodeStringArray(strValue));
break;
case "Clan name whitelist":
whitelist_clans = new List(CPluginVariable.DecodeStringArray(strValue));
break;
case "Send ProconRulz Log messages to:":
log_file = (LogFileEnum)Enum.Parse(typeof(LogFileEnum), strValue);
break;
default:
break;
}
}
catch (Exception ex)
{
PrintException(ex);
}
}
// Allow procon to READ current values of program globals (and write values to server_ip.cfg)
public List GetDisplayPluginVariables()
{
try
{
List lst = new List();
lst.Add(new CPluginVariable("Game",
CreateEnumString(typeof(GameIdEnum)), game_id.ToString()));
lst.Add(new CPluginVariable("Delay before kill", typeof(int), kill_delay));
lst.Add(new CPluginVariable("Yell seconds", typeof(int), yell_delay));
//lst.Add(new CPluginVariable("Player keeps items on respawn",
// CreateEnumString(typeof(ReserveItemEnum)), reservationMode.ToString()));
lst.Add(new CPluginVariable("EA Rules of Conduct read and accepted",
typeof(enumBoolYesNo), roc_read));
lst.Add(new CPluginVariable("Protect these players from Kick or Kill",
CreateEnumString(typeof(ProtectEnum)), protect_players.ToString()));
lst.Add(new CPluginVariable("Trace rules", typeof(enumBoolYesNo), trace_rules));
lst.Add(new CPluginVariable("Rules", typeof(string[]), unparsed_rules.ToArray()));
lst.Add(new CPluginVariable("Rulz .txt filenames",
typeof(string[]), rulz_filenames.ToArray()));
lst.Add(new CPluginVariable("Player name whitelist",
typeof(string[]), whitelist_players.ToArray()));
lst.Add(new CPluginVariable("Clan name whitelist",
typeof(string[]), whitelist_clans.ToArray()));
lst.Add(new CPluginVariable("Send ProconRulz Log messages to:",
CreateEnumString(typeof(LogFileEnum)), log_file.ToString()));
return lst;
}
catch (Exception ex)
{
PrintException(ex);
return new List();
}
}
public List GetPluginVariables()
{
return GetDisplayPluginVariables();
}
#endregion
#region Parse the rulz
//**********************************************************************************************
//**********************************************************************************************
// PARSE THE RULES THAT HAVE BEEN READ FROM THE PROCON SETTINGS (Configs\server_ip.cfg)
//**********************************************************************************************
//**********************************************************************************************
// simple routine to output a console message when parse_rules chokes on a user input rule
void parse_error(string key, string rule)
{
WriteConsole(
String.Format("ProconRulz ^1SKIPPING RULE: Bad \"{0}\" clause in your rule \"{1}\" ",
key, rule));
return;
}
// Here is where we try and parse the list of unparsed rules that
// have been read from the server_ip.cfg
// Input is the List unparsed_rules, as read from server_ip.cfg
// Output is the List parsed_rules
// each rule is split into parts, with each part split into fragments e.g.
// "team attack;teamsize 8;kit recon 2;say No More Snipers!;kill"
// the first part is "team attack"
// the first fragment is "team"
void parse_rules()
{
int rule_id = 1; // initialise first value of rule identifier
// start with a default trigger of "On Spawn" and update as rulz with triggers are parsed
rule_prefix = new ParsedRule();
string rule_string_buffer = "empty"; // used to hold current rule for WriteConsole in case of exception
try
{
if (!plugin_enabled) return;
//WriteLog(String.Format("ProconRulz: Parsing your rules"));
parsed_rules.Clear(); // start with an empty list
ParsedRule parsed_rule = new ParsedRule(); // start with blank parsed rule
parsed_rule.id = rule_id++;
bool first_rule = true;
// Concatenate user rules from settings AND user rulz files
List all_unparsed = new List(unparsed_rules);
foreach (KeyValuePair rulz in filez_rulz)
{
all_unparsed.AddRange(rulz.Value);
}
WriteConsole("ProconRulz: loading " + all_unparsed.Count.ToString() + " rulz");
foreach (string rule_string in all_unparsed)
{
rule_string_buffer = rule_string;
WriteDebugInfo("ProconRulz: parsing " + rule_string);
if ((rule_string.Length > 0 && rule_string[0] != '+') || rule_string.Length == 0)
{ // this is not a rule continuation, store accumulated rule
if (!first_rule)
{
parsed_rules.Add(parsed_rule); // add previous rule
WriteDebugInfo("ProconRulz: storing rule " + parsed_rule.id.ToString() +
" as " + parsed_rule.unparsed_rule);
parsed_rule = new ParsedRule(); // start new rule
parsed_rule.trigger = TriggerEnum.Void; // we can check this to see if rule has trigger
parsed_rule.id = rule_id++;
}
}
if (parse_rule(ref parsed_rule, rule_string))
{
first_rule = false;
WriteDebugInfo("ProconRulz: parsed ok");
parsed_rule.unparsed_rule += rule_string;
// if rule did NOT have an On trigger, prepend the rule_prefix...
if (parsed_rule.trigger == TriggerEnum.Void)
{
parsed_rule.trigger = rule_prefix.trigger;
List l = new List();
foreach (PartClass p in rule_prefix.parts) l.Add(p);
foreach (PartClass p in parsed_rule.parts) l.Add(p);
parsed_rule.parts = l;
}
else
{
// rule DID have a trigger, so use this rule as new rule_prefix...
rule_prefix.trigger = parsed_rule.trigger;
rule_prefix.parts = new List(parsed_rule.parts);
}
}
} // loop to next rule
if (!first_rule) // flush last rule
{
parsed_rules.Add(parsed_rule);
WriteDebugInfo("ProconRulz: storing last rule " + parsed_rule.id.ToString() +
" as " + parsed_rule.unparsed_rule);
}
WriteConsole(string.Format("ProconRulz: {0} rules loaded", rule_id-1));
// run 'On Init' rulz
OnInit();
}
catch (Exception ex)
{
WriteConsole(String.Format("^1ProconRulz: Exception occurred parsing your rules"));
WriteConsole(String.Format("^1ProconRulz: rule string was: "+rule_string_buffer));
PrintException(ex);
}
}
// parse a single rule string e.g. "On Kill;Damage SniperRifle;Kill 300"
// return 'true' if parsed successfully
private bool parse_rule(ref ParsedRule parsed_rule, string rule_string)
{
if (rule_string == null || rule_string.Length < 4)
{
return false;
}
// only parse if this rule is not a comment
if (rule_string[0] == '#') parsed_rule.comment = true;
else
{
string parse_string;
if (rule_string[0] == '+')
{
parse_string = ";" + rule_string.Substring(1);
}
else
{
parse_string = rule_string;
}
string[] parts
= parse_string.Replace("%3b", ";").Split(
new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string part in parts)
{
// note we use Trim() to take off leading/trailing whitespace from part
if (!parse_part(ref parsed_rule, part.Trim(), false)) return false;
} // loop to next part
} // looping through rule parts completed - now add to list "parsed_rules"
return true;
}
// parse a 'part' of a rule, e.g. "Damage SniperRifle" or "TargetAction Kill 300"
// returns 'true' if parse succeeded
private bool parse_part(ref ParsedRule parsed_rule, string part, bool target_action)
{
try
{
bool rule_fail = false; // flag used to skip rest of rule on a parse error
bool parsed_int = false; // flag used to confirm a part format with optional int was parsed ok
// create action to be added to parsed_rule if we need it
PartClass new_action = new PartClass();
new_action.target_action = target_action;
string[] fragments = quoted_split(part); // v40b update to allow quoted strings
if (fragments == null || fragments.Length == 0) return true;
switch (fragments[0].ToLower())
{
// TRIGGER ("On Spawn" or "On Kill")
case "on": // trigger the rule on Spawn or a Kill
rule_fail = !parse_on(ref parsed_rule, fragments);
break;
// CONDITIONS: i.e. what is needed for this rule to fire
case "headshot": // e.g. "Headshot"
rule_fail = !parse_headshot(ref parsed_rule, false);
break;
case "protected": // e.g. "Protected" - this player is on reserved slots list
rule_fail = !parse_protected(ref parsed_rule, false);
break;
case "admin": // e.g. "Admin" - this player is an admin
rule_fail = !parse_admin(ref parsed_rule, false);
break;
case "admins": // e.g. "Admins" - admins are on the server
rule_fail = !parse_admins(ref parsed_rule, false);
break;
case "team": // e.g. "Team Attack" or defend
rule_fail = !parse_team(ref parsed_rule, fragments, false);
break;
case "ping": // e.g. "Ping 300"
rule_fail = !parse_ping(ref parsed_rule, fragments, false);
break;
case "teamsize":
// e.g. "Teamsize 8" this rule only applies to teams this small or smaller
rule_fail = !parse_teamsize(ref parsed_rule, fragments, false);
break;
case "map": // e.g. "Map Oasis" this rule only applies to map oasis
rule_fail = !parse_map(ref parsed_rule, part, false);
break;
case "mapmode": // e.g. "MapMode Rush" this rule only applies to maps in Rush mode
rule_fail = !parse_mapmode(ref parsed_rule, fragments, false);
break;
case "channel": // e.g. "Channel global", this rule only applies to global chat.
rule_fail = !parse_channel(ref parsed_rule, fragments, false);
break;
case "kit": // e.g. "Kit Recon 2" - max 2 recons on the team
rule_fail = !parse_kit(ref parsed_rule, fragments, false);
break;
case "weapon": // e.g. "Weapon AUG 3"
rule_fail = !parse_weapon(ref parsed_rule, fragments, false);
break;
case "spec": // e.g. "Spec sp_vdamage 3"
rule_fail = !parse_spec(ref parsed_rule, fragments, false);
break;
case "damage": // e.g. "Damage SniperRifle 8"
rule_fail = !parse_damage(ref parsed_rule, fragments, false);
break;
case "teamkit": // e.g. "TeamKit Recon 2" - max 2 recons on the team
rule_fail = !parse_teamkit(ref parsed_rule, fragments, false);
break;
case "teamweapon": // e.g. "TeamWeapon AUG 3"
rule_fail = !parse_teamweapon(ref parsed_rule, fragments, false);
break;
case "teamspec": // e.g. "TeamSpec sp_vdamage 3"
rule_fail = !parse_teamspec(ref parsed_rule, fragments, false);
break;
case "teamdamage": // e.g. "TeamDamage SniperRifle 8"
rule_fail = !parse_teamdamage(ref parsed_rule, fragments, false);
break;
case "range": // e.g. "Range 100"
rule_fail = !parse_range(ref parsed_rule, fragments, false);
break;
case "not": // e.g. "Not Damage SniperRifle"
rule_fail = !parse_not(ref parsed_rule, fragments, part);
break;
case "count": // e.g. "Count 8" - how many times PLAYER can trigger this rule
case "playercount":
// e.g. "PlayerCount 8" - how many times PLAYER can trigger this rule
rule_fail = !parse_count(ref parsed_rule, fragments, false);
break;
case "teamcount": // e.g. "TeamCount 8" - how many times TEAM can trigger this rule
rule_fail = !parse_teamcount(ref parsed_rule, fragments, false);
break;
case "servercount":
// e.g. "ServerCount 8" - how many times SERVER can trigger this rule
rule_fail = !parse_servercount(ref parsed_rule, fragments, false);
break;
case "playerfirst":
case "teamfirst":
case "serverfirst":
case "playeronce":
rule_fail = !parse_first(ref parsed_rule, fragments[0].ToLower(), false);
break;
case "rate": // e.g. "Rate 5 20" this rule triggered 5 times in 20 seconds
rule_fail = !parse_rate(ref parsed_rule, fragments, false);
break;
case "text": // e.g. "On Say;Text ofc;Yell OFc 4 Ever"
rule_fail = !parse_text(ref parsed_rule, part, false);
break;
case "targetplayer": // e.g. "TargetPlayer" (extract playername from say text)
rule_fail = !parse_targetplayer(ref parsed_rule, part, false);
break;
case "incr": // e.g. "Incr kill_count"
rule_fail = !parse_incr(ref parsed_rule, fragments);
break;
case "decr": // e.g. "Decr kill_count"
rule_fail = !parse_decr(ref parsed_rule, fragments);
break;
case "set": // e.g. "Set kill_count 0"
rule_fail = !parse_set(ref parsed_rule, fragments);
break;
case "if": // e.g. "If kill_count > 7"
rule_fail = !parse_test(ref parsed_rule, part, false); //LOOK FOR WHERE THE ACTUAL BOOLEAN STATEMENT IS EVALUATED. We need to figure out where the 'AND' and 'OR' keywords are.
break;
// ACTIONS i.e. what to do when conditions are true
// ************************************************
case "say": // e.g. "Say No more Snipers!"
if (part.Length < 5)
{
parse_error("Say", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.Say;
new_action.string_list.Add(part.Substring(4));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "playersay": // e.g. "PlayerSay No more Snipers!"
if (part.Length < 11)
{
parse_error("PlayerSay", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.PlayerSay;
new_action.string_list.Add(part.Substring(10));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "squadsay": // e.g. "SquadSay No more Snipers!"
if (part.Length < 10)
{
parse_error("SquadSay", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.SquadSay;
new_action.string_list.Add(part.Substring(9));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "teamsay": // e.g. "TeamSay No more Snipers!"
if (part.Length < 9)
{
parse_error("TeamSay", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.TeamSay;
new_action.string_list.Add(part.Substring(8));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "victimsay": // e.g. "VictimSay You were killed by %p%, range %r%!"
if (part.Length < 11)
{
parse_error("VictimSay", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.VictimSay;
new_action.string_list.Add(part.Substring(10));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "adminsay": // e.g. "AdminSay HACKER WARNING on %p% (5 headshots in 15 seconds)!!"
if (part.Length < 10)
{
parse_error("AdminSay", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.AdminSay;
new_action.string_list.Add(part.Substring(9));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "yell":
if (part.Length < 6)
{
parse_error("Yell", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.Yell;
parsed_int = false; // assume parsing yell_delay fails
try
{
new_action.int1 = Int32.Parse(fragments[1]); // try and pick up yell delay (seconds)
new_action.string_list.Add(String.Join(" ", fragments, 2, fragments.Length-2 )); // rest of string
parsed_int = true;
}
catch { }
if (!parsed_int) // we didn't parse a yell delay so use default
{
new_action.int1 = yell_delay;
new_action.string_list.Add(part.Substring(5));
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "playeryell": // e.g. "PlayerYell No more Snipers!"
if (part.Length < 12)
{
parse_error("PlayerYell", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.PlayerYell;
parsed_int = false; // assume parsing yell_delay fails
try
{
new_action.int1 = Int32.Parse(fragments[1]); // try and pick up yell delay (seconds)
new_action.string_list.Add(String.Join(" ", fragments, 2, fragments.Length-2 )); // rest of string
parsed_int = true;
}
catch { }
if (!parsed_int) // we didn't parse a yell delay so use default
{
new_action.int1 = yell_delay;
new_action.string_list.Add(part.Substring(11));
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "squadyell": // e.g. "SquadYell No more Snipers!"
if (part.Length < 11)
{
parse_error("SquadYell", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.SquadYell;
parsed_int = false; // assume parsing yell_delay fails
try
{
new_action.int1 = Int32.Parse(fragments[1]); // try and pick up yell delay (seconds)
new_action.string_list.Add(String.Join(" ", fragments, 2, fragments.Length-2 )); // rest of string
parsed_int = true;
}
catch { }
if (!parsed_int) // we didn't parse a yell delay so use default
{
new_action.int1 = yell_delay;
new_action.string_list.Add(part.Substring(10));
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "teamyell": // e.g. "TeamYell No more Snipers!"
if (part.Length < 10)
{
parse_error("TeamYell", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.TeamYell;
parsed_int = false; // assume parsing yell_delay fails
try
{
new_action.int1 = Int32.Parse(fragments[1]); // try and pick up yell delay (seconds)
new_action.string_list.Add(String.Join(" ", fragments, 2, fragments.Length-2 )); // rest of string
parsed_int = true;
}
catch { }
if (!parsed_int) // we didn't parse a yell delay so use default
{
new_action.int1 = yell_delay;
new_action.string_list.Add(part.Substring(9));
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "log":
if (part.Length < 5)
{
parse_error("Log", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.Log;
new_action.string_list.Add(part.Substring(4));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "all": // say and yell and log
if (part.Length < 5)
{
parse_error("All", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.All;
new_action.string_list.Add(part.Substring(4));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "both": // say and yell
if (part.Length < 6)
{
parse_error("Both", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.Both;
new_action.string_list.Add(part.Substring(5));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "playerboth": // say and yell to a player
if (part.Length < 12)
{
parse_error("PlayerBoth", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.PlayerBoth;
new_action.string_list.Add(part.Substring(11));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "kill": // kill player that triggered this rule
new_action.part_type = PartEnum.Kill;
if (fragments.Length == 2) new_action.string_list.Add(fragments[1]);
else new_action.string_list.Add(kill_delay.ToString());
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "kick": // kick player that triggered this rule
new_action.part_type = PartEnum.Kick;
if (fragments.Length >= 2) new_action.string_list.Add(part.Substring(5));
else new_action.string_list.Add("Kicked automatically");
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "ban": // ban player that triggered this rule
new_action.part_type = PartEnum.Ban;
if (fragments.Length >= 2) new_action.string_list.Add(part.Substring(4));
else new_action.string_list.Add("[%p%] Banned automatically");
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "tempban": // temp ban player that triggered this rule
new_action.part_type = PartEnum.TempBan;
// try and pick up number of seconds for ban from fragments[1]
bool tempban_default = true;
if (fragments.Length >= 2)
try
{
new_action.int1 = Int32.Parse(fragments[1]);
tempban_default = false;
}
catch
{ new_action.int1 = ban_delay; }
else new_action.int1 = ban_delay;
// now try and dig out message
// "TempBan" or "TempBan 7777"
if (fragments.Length == 1 || (!tempban_default && fragments.Length == 2))
{
new_action.string_list.Add("[%p%] automatic temp ban");
}
// TempBan
else if (tempban_default && fragments.Length >= 2)
{
new_action.string_list.Add(part.Substring(8));
}
// TempBan
else if (!tempban_default && fragments.Length > 2)
{
new_action.string_list.Add(part.Substring(8 + fragments[1].Length));
}
else
{
parse_error("TempBan", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "pbban": // ban player that triggered this rule via PunkBuster
new_action.part_type = PartEnum.PBBan;
if (fragments.Length >= 2) new_action.string_list.Add(part.Substring(6));
else new_action.string_list.Add("[%p%] Banned automatically");
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "pbkick": // temp ban player that triggered this rule (i.e. kick with timeout via PunkBuster)
new_action.part_type = PartEnum.PBKick;
// try and pick up number of MINUTES for ban from fragments[1]
bool pbkick_default = true;
if (fragments.Length >= 2)
try
{
new_action.int1 = Int32.Parse(fragments[1]);
pbkick_default = false;
}
catch
{ new_action.int1 = 0; } // default is NO temp ban on PB kick
else new_action.int1 = 0;
// now try and dig out message
// "PBKick" or "PBKick 77"
if (fragments.Length == 1 || (!pbkick_default && fragments.Length == 2))
{
new_action.string_list.Add("[%p%] automatic temp kick/ban");
}
// PBKick
else if (pbkick_default && fragments.Length >= 2)
{
new_action.string_list.Add(part.Substring(7));
}
// PBKick
else if (!pbkick_default && fragments.Length > 2)
{
new_action.string_list.Add(part.Substring(7 + fragments[1].Length));
}
else
{
parse_error("PBKick", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "playerblock": // block player name from using item
new_action.part_type = PartEnum.PlayerBlock;
if (fragments.Length == 2) new_action.string_list.Add(fragments[1]); // item key
else new_action.string_list.Add("unknown");
parsed_rule.parts.Add(new_action);
break;
case "targetconfirm": // trigger previous TargetAction actions from this player
if (fragments.Length != 1) parse_error("TargetConfirm", parsed_rule.unparsed_rule);
new_action.part_type = PartEnum.TargetConfirm;
new_action.string_list.Add("");
parsed_rule.parts.Add(new_action);
break;
case "targetcancel": // cancel previous TargetAction actions from this player
if (fragments.Length != 1) parse_error("TargetCancel", parsed_rule.unparsed_rule);
new_action.part_type = PartEnum.TargetCancel;
new_action.string_list.Add("");
parsed_rule.parts.Add(new_action);
break;
case "targetaction": // store delayed actions on target from this player
rule_fail = !parse_part(ref parsed_rule, part.Substring(13), true);
break;
case "exec": // e.g. "Exec levelVars.set level levels/mp_007gr vehiclesDisabled false"
if (part.Length < 6)
{
parse_error("Execute", parsed_rule.unparsed_rule); rule_fail = true; break;
}
new_action.part_type = PartEnum.Execute;
new_action.string_list.Add(part.Substring(5));
new_action.has_count = has_a_count(new_action);
parsed_rule.parts.Add(new_action);
break;
case "continue":
new_action.part_type = PartEnum.Continue;
new_action.string_list.Add("");
parsed_rule.parts.Add(new_action);
break;
case "end":
new_action.part_type = PartEnum.End;
new_action.string_list.Add("");
parsed_rule.parts.Add(new_action);
break;
default:
WriteConsole(String.Format("^1ProconRulz: Unrecognised rule {0}",
parsed_rule.unparsed_rule));
rule_fail = true;
break;
}
return !rule_fail;
}
catch (Exception ex)
{
WriteConsole(String.Format("^1ProconRulz: Exception occurred parsing your rules"));
WriteConsole(String.Format("^1ProconRulz: Rule was \"{0}\"", parsed_rule.unparsed_rule));
WriteConsole(String.Format("^1ProconRulz: Part that failed was \"{0}\"", part));
PrintException(ex);
return false;
}
}
private bool parse_on(ref ParsedRule parsed_rule, string[] fragments)
{
//WriteDebugInfo(String.Format("ProconRulz: Parsing On"));
if (fragments.Length!=2)
{
parse_error("On", parsed_rule.unparsed_rule);
return false;
}
if (fragments[1].ToLower().StartsWith("roundover"))
{
parsed_rule.trigger = TriggerEnum.RoundOver;
return true;
}
if (fragments[1].ToLower().StartsWith("round"))
{
parsed_rule.trigger = TriggerEnum.Round;
return true;
}
if (fragments[1].ToLower().StartsWith("join"))
{
parsed_rule.trigger = TriggerEnum.Join;
return true;
}
if (fragments[1].ToLower().StartsWith("leave"))
{
parsed_rule.trigger = TriggerEnum.Leave;
return true;
}
if (fragments[1].ToLower().StartsWith("spawn"))
{
parsed_rule.trigger = TriggerEnum.Spawn;
return true;
}
if (fragments[1].ToLower().StartsWith("kill"))
{
parsed_rule.trigger = TriggerEnum.Kill;
return true;
}
if (fragments[1].ToLower().StartsWith("teamkill"))
{
parsed_rule.trigger = TriggerEnum.TeamKill;
return true;
}
if (fragments[1].ToLower().StartsWith("suicide"))
{
parsed_rule.trigger = TriggerEnum.Suicide;
return true;
}
if (fragments[1].ToLower().StartsWith("playerblock"))
{
parsed_rule.trigger = TriggerEnum.PlayerBlock;
return true;
}
if (fragments[1].ToLower().StartsWith("serversay"))
{
parsed_rule.trigger = TriggerEnum.ServerSay;
return true;
}
if (fragments[1].ToLower().StartsWith("say"))
{
parsed_rule.trigger = TriggerEnum.Say;
return true;
}
if (fragments[1].ToLower().StartsWith("init"))
{
parsed_rule.trigger = TriggerEnum.Init;
return true;
}
parse_error("On", parsed_rule.unparsed_rule);
return false;
}
private bool parse_headshot(ref ParsedRule parsed_rule, bool negated)
{
PartClass c = new PartClass();
c.part_type = PartEnum.Headshot;
c.negated = negated;
parsed_rule.parts.Add(c);
return true;
}
// this player is on reserved slots list
private bool parse_protected(ref ParsedRule parsed_rule, bool negated)
{
PartClass c = new PartClass();
c.part_type = PartEnum.Protected;
c.negated = negated;
parsed_rule.parts.Add(c);
return true;
}
// this player is an admin
private bool parse_admin(ref ParsedRule parsed_rule, bool negated)
{
PartClass c = new PartClass();
c.part_type = PartEnum.Admin;
c.negated = negated;
parsed_rule.parts.Add(c);
return true;
}
// admins are currently on the server
private bool parse_admins(ref ParsedRule parsed_rule, bool negated)
{
PartClass c = new PartClass();
c.part_type = PartEnum.Admins;
c.negated = negated;
parsed_rule.parts.Add(c);
return true;
}
private bool parse_team(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Team", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Team;
c.negated = negated;
c.string_list = item_keys(fragments[negated ? 2 : 1].ToLower());
parsed_rule.parts.Add(c);
return true;
}
private bool parse_ping(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Ping", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Ping;
c.negated = negated;
try
{
c.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
}
catch
{
parse_error("Ping", parsed_rule.unparsed_rule);
return false;
}
parsed_rule.parts.Add(c);
return true;
}
private bool parse_teamsize(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Teamsize", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Teamsize;
c.negated = negated;
try
{
c.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
}
catch
{
parse_error("Teamsize", parsed_rule.unparsed_rule);
return false;
}
parsed_rule.parts.Add(c);
return true;
}
private bool parse_map(ref ParsedRule parsed_rule, string part, bool negated)
{
if (part.Length < 5) { parse_error("Map", parsed_rule.unparsed_rule); return false; }
PartClass c = new PartClass();
c.part_type = PartEnum.Map;
c.negated = negated;
c.string_list = negated ? item_keys(part.Substring(8).ToLower()) : item_keys(part.Substring(4).ToLower());
parsed_rule.parts.Add(c);
return true;
}
private bool parse_mapmode(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length != (negated ? 3 : 2))
{
parse_error("MapMode", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.MapMode;
c.negated = negated;
c.string_list = item_keys(fragments[negated ? 2 : 1]);
parsed_rule.parts.Add(c);
return true;
}
private bool parse_channel(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length != (negated ? 3 : 2))
{
parse_error("Channel", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Channel;
c.negated = negated;
c.string_list = item_keys(fragments[negated ? 2 : 1]);
parsed_rule.parts.Add(c);
return true;
}
private bool parse_kit(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Kit", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Kit;
c.negated = negated;
if (fragments.Length == (negated ? 4 : 3))
try
{
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
}
catch
{
parse_error("Kit", parsed_rule.unparsed_rule);
return false;
}
try
{
c.string_list = item_keys(fragments[negated ? 2 : 1]);
foreach (string kit_key in c.string_list)
{
try
{
Kits k = (Kits)Enum.Parse(typeof(Kits), kit_key, true);
}
catch (ArgumentException)
{
WriteConsole(String.Format("ProconRulz: ^1Warning, kit {0} not found in Procon (but you can still use the key in ProconRulz)", kit_key));
}
}
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
}
catch { parse_error("Kit", parsed_rule.unparsed_rule); return false; }
return true;
}
// updated to allow '&' char in weapon key instead of a space
// this is complicated by the fact that weapon keys can
// INCLUDE SPACES (thankyou EA) eg "M1A1 Thompson" and heli weapons
// Weapon AUG
// Not Weapon AUG
// Weapon AUG 3
// Not Weapon AUG 3
// Weapon M1A1 Thompson
// Weapon M1A1 Thompson 3
// Not Weapon M1A1 Thompson
// Not Weapon M1A1 Thompson 3
// multiple weapon keys can be separated with rulz_item_separator E.g. Weapon SMAW,RPG-7
private bool parse_weapon(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
//WriteConsole(String.Format("rule {0}", parsed_rule.unparsed_rule));
//WriteConsole(String.Format("length {0}, negated {1}", fragments.Length, negated));
//WriteConsole(String.Format("fragments {0}", String.Join("---",fragments)));
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Weapon", parsed_rule.unparsed_rule);
return false;
}
// ok lets try and figure out what we've got in the rule
List weapon_keys = item_keys((negated ? fragments[2] : fragments[1]));
// try and make a count out of the last fragment
int weapon_count = -1;
try
{
weapon_count = Int32.Parse(fragments[fragments.Length - 1]);
}
catch
{
}
//WriteConsole(String.Format("weapon_count {0}", weapon_count));
PartClass c = new PartClass();
c.part_type = PartEnum.Weapon;
c.negated = negated;
if (weapon_count > -1) c.int1 = weapon_count;
foreach (string weapon_key in weapon_keys)
{
if (!weaponDefines.Contains(weapon_key))
WriteConsole(String.Format("ProconRulz: ^1Warning, weapon {0} not found in Procon (but you can still use the key in ProconRulz)", weapon_key));
}
c.string_list = weapon_keys;
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
//else { parse_error("Weapon", parsed_rule.unparsed_rule); return false; }
return true;
}
private bool parse_spec(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Spec", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Spec;
c.negated = negated;
if (fragments.Length == (negated ? 4 : 3))
try
{
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
}
catch
{
parse_error("Spec", parsed_rule.unparsed_rule);
return false;
}
List spec_keys = item_keys(fragments[negated ? 2 : 1]);
foreach (string spec_key in spec_keys)
{
if (!specDefines.Contains(spec_key))
WriteConsole(String.Format("ProconRulz: ^1Warning, Specialization {0} not found in Procon (but you can still use the key in ProconRulz)", spec_key));
}
c.string_list = spec_keys;
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
return true;
}
private bool parse_damage(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("Damage", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Damage;
c.negated = negated;
if (fragments.Length == (negated ? 4 : 3))
try
{
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
}
catch
{
parse_error("Damage", parsed_rule.unparsed_rule);
return false;
}
try
{
c.string_list = item_keys(fragments[negated ? 2 : 1]);
foreach (string damage_key in c.string_list)
{
try
{
DamageTypes d = (DamageTypes)Enum.Parse(typeof(DamageTypes), damage_key, true);
}
catch (ArgumentException)
{
WriteConsole(String.Format("ProconRulz: ^1Warning, damage {0} not found in Procon (but you can still use the key in ProconRulz)", damage_key));
}
}
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
}
catch { parse_error("Damage", parsed_rule.unparsed_rule); return false; }
return true;
}
// count of spawned Kits on team
private bool parse_teamkit(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length != (negated ? 4 : 3))
{
parse_error("TeamKit", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.TeamKit;
c.negated = negated;
try
{
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
c.string_list = item_keys(fragments[negated ? 2 : 1]);
foreach (string kit_key in c.string_list)
{
try
{
Kits k = (Kits)Enum.Parse(typeof(Kits), kit_key, true);
}
catch (ArgumentException)
{
WriteConsole(String.Format("ProconRulz: ^1Warning, kit {0} not found in Procon (but you can still use the key in ProconRulz)", kit_key));
}
}
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
}
catch { parse_error("TeamKit", parsed_rule.unparsed_rule); return false; }
return true;
}
// see parse_weapon for more comments (multi-word weapon keys)
private bool parse_teamweapon(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
//WriteConsole(String.Format("rule {0}", parsed_rule.unparsed_rule));
//WriteConsole(String.Format("length {0}, negated {1}", fragments.Length, negated));
//WriteConsole(String.Format("fragments {0}", String.Join("---",fragments)));
if (fragments.Length < (negated ? 3 : 2))
{
parse_error("TeamWeapon", parsed_rule.unparsed_rule);
return false;
}
// ok lets try and figure out what we've got in the rule
List weapon_keys = item_keys(negated ? fragments[2] : fragments[1]);
// try and make a count out of the last fragment
int weapon_count = -1;
try
{
weapon_count = Int32.Parse(fragments[fragments.Length - 1]);
}
catch
{
}
//WriteConsole(String.Format("weapon_count {0}", weapon_count));
//WriteConsole(String.Format("weapon_key {0}", weapon_key));
PartClass c = new PartClass();
c.part_type = PartEnum.TeamWeapon;
c.negated = negated;
if (weapon_count > -1) c.int1 = weapon_count;
foreach (string weapon_key in weapon_keys)
{
if (!weaponDefines.Contains(weapon_key))
WriteConsole(String.Format("ProconRulz: ^1Warning, weapon {0} not found in Procon (but you can still use the key in ProconRulz)", weapon_key));
}
c.string_list = weapon_keys;
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
return true;
}
// count of spawned specializations on team
private bool parse_teamspec(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length != (negated ? 4 : 3))
{
parse_error("TeamSpec", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.TeamSpec;
c.negated = negated;
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
List spec_keys = item_keys(fragments[negated ? 2 : 1]);
foreach (string spec_key in spec_keys)
{
if (!specDefines.Contains(spec_key))
WriteConsole(String.Format("ProconRulz: ^1Warning, Specialization {0} not found in Procon (but you can still use the key in ProconRulz)", spec_key));
}
c.string_list = spec_keys;
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
return true;
}
private bool parse_teamdamage(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length != (negated ? 4 : 3))
{
parse_error("TeamDamage", parsed_rule.unparsed_rule);
return false;
}
PartClass c = new PartClass();
c.part_type = PartEnum.Damage;
c.negated = negated;
try
{
c.int1 = Int32.Parse(fragments[negated ? 3 : 2]);
c.string_list = item_keys(fragments[negated ? 2 : 1]);
foreach (string damage_key in c.string_list)
{
try
{
DamageTypes d = (DamageTypes)Enum.Parse(typeof(DamageTypes), damage_key, true);
}
catch (ArgumentException)
{
WriteConsole(String.Format("ProconRulz: ^1Warning, damage {0} not found in Procon (but you can still use the key in ProconRulz)", damage_key));
}
}
parsed_rule.parts.Add(c);
spawn_counts.watch(c.string_list);
}
catch { parse_error("TeamDamage", parsed_rule.unparsed_rule); return false; }
return true;
}
// range of the kill
private bool parse_range(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length == (negated ? 3 : 2))
{
PartClass c = new PartClass();
c.part_type = PartEnum.Range;
c.negated = negated;
c.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
parsed_rule.parts.Add(c);
return true;
}
parse_error("Range", parsed_rule.unparsed_rule);
return false;
}
// "Not " e.g. "Not Team Attack"
// this implementation is inelegant - I should be recursively calling parse_part(...)
private bool parse_not(ref ParsedRule parsed_rule, string[] fragments, string part)
{
if (fragments.Length < 2)
{
parse_error("Not", parsed_rule.unparsed_rule);
return false;
}
switch (fragments[1].ToLower())
{
case "if":
return parse_test(ref parsed_rule, part, true);
case "text":
return parse_text(ref parsed_rule, part, true);
case "headshot":
return parse_headshot(ref parsed_rule, true);
case "protected":
return parse_protected(ref parsed_rule, true);
case "targetplayer":
return parse_targetplayer(ref parsed_rule, part, true);
case "admin":
return parse_admin(ref parsed_rule, true);
case "admins":
return parse_admins(ref parsed_rule, true);
case "ping":
return parse_ping(ref parsed_rule, fragments, true);
case "team":
return parse_team(ref parsed_rule, fragments, true);
case "teamsize":
return parse_teamsize(ref parsed_rule, fragments, true);
case "map":
return parse_map(ref parsed_rule, part, true);
case "mapmode":
return parse_mapmode(ref parsed_rule, fragments, true);
case "channel":
return parse_channel(ref parsed_rule, fragments, true);
case "kit":
return parse_kit(ref parsed_rule, fragments, true);
case "spec":
return parse_spec(ref parsed_rule, fragments, true);
case "weapon":
return parse_weapon(ref parsed_rule, fragments, true);
case "damage":
return parse_damage(ref parsed_rule, fragments, true);
case "teamkit":
return parse_teamkit(ref parsed_rule, fragments, true);
case "teamspec":
return parse_teamspec(ref parsed_rule, fragments, true);
case "teamweapon":
return parse_teamweapon(ref parsed_rule, fragments, true);
case "teamdamage":
return parse_teamdamage(ref parsed_rule, fragments, true);
case "range":
return parse_range(ref parsed_rule, fragments, true);
case "count":
case "playercount":
return parse_count(ref parsed_rule, fragments, true);
case "teamcount":
return parse_teamcount(ref parsed_rule, fragments, true);
case "servercount":
return parse_servercount(ref parsed_rule, fragments, true);
case "playerfirst":
case "teamfirst":
case "serverfirst":
case "playeronce":
return parse_first(ref parsed_rule, fragments[1].ToLower(), true);
case "rate":
return parse_rate(ref parsed_rule, fragments, true);
default:
parse_error("Not", parsed_rule.unparsed_rule);
break;
}
return false;
}
private bool parse_count(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length == (negated ? 3 : 2))
{
PartClass p = new PartClass();
p.part_type = PartEnum.Count;
p.negated = negated;
p.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
p.has_count = true;
parsed_rule.parts.Add(p);
return true;
}
parse_error("Count", parsed_rule.unparsed_rule);
return false;
}
private bool parse_teamcount(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length == (negated ? 3 : 2))
{
PartClass p = new PartClass();
p.part_type = PartEnum.TeamCount;
p.negated = negated;
p.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
p.has_count = true;
parsed_rule.parts.Add(p);
return true;
}
parse_error("TeamCount", parsed_rule.unparsed_rule);
return false;
}
private bool parse_servercount(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
if (fragments.Length == (negated ? 3 : 2))
{
PartClass p = new PartClass();
p.part_type = PartEnum.ServerCount;
p.negated = negated;
p.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
p.has_count = true;
parsed_rule.parts.Add(p);
return true;
}
parse_error("ServerCount", parsed_rule.unparsed_rule);
return false;
}
private bool parse_rate(ref ParsedRule parsed_rule, string[] fragments, bool negated)
{
PartClass p = new PartClass();
if (fragments.Length != (negated ? 4 : 3))
{
parse_error("Rate", parsed_rule.unparsed_rule);
return false;
}
try
{
p.part_type = PartEnum.Rate;
p.negated = negated;
p.int1 = Int32.Parse(fragments[negated ? 2 : 1]);
p.int2 = Int32.Parse(fragments[negated ? 3 : 2]);
}
catch { parse_error("Rate", parsed_rule.unparsed_rule); return false; }
parsed_rule.parts.Add(p);
return true;
}
// Here we translate some convenience conditions into actual equivalents:
// PlayerFirst -> Not PlayerCount 1 (i.e. player count is not > 1, i.e. count MUST be 1)
// TeamFirst -> Not TeamCount 1
// ServerFirst -> Not ServerCount 1
// PlayerOnce -> Not Rate 2 100000 (tricky to explain... rule has NOT fired twice in 100000 seconds,
// which is only true the FIRST time the rule fires for this player
// because the time period is long enough that the second time it fires
// will be inside the 100000 second window so the condition will fail
// the second (and later) time.
// This rule takes advantage of the fact that RATES continue across
// new round starts. Rates reset for a player on a new round when they are
// NOT online. Told you it was tricky).
private bool parse_first(ref ParsedRule parsed_rule, string condition, bool negated)
{
PartClass p = new PartClass();
p.int1 = 1; // 1 for 'first's, will change to 2 for PlayerOnce...
p.negated = !negated; // i.e. "Not PlayerFirst" -> "PlayerCount 1", i.e. the Not inverts
switch (condition)
{
case "playerfirst":
p.part_type = PartEnum.PlayerCount;
break;
case "teamfirst":
p.part_type = PartEnum.TeamCount;
break;
case "serverfirst":
p.part_type = PartEnum.ServerCount;
break;
case "playeronce":
p.part_type = PartEnum.Rate;
p.int1 = 2; // is 1 for the above conditions, change to 2 here
p.int2 = 100000; // period for Rate set to 100000 seconds i.e. about a day
break;
}
p.has_count = true;
parsed_rule.parts.Add(p);
return true;
}
private bool parse_text(ref ParsedRule parsed_rule, string part, bool negated)
{
if (part.Length < 6) { parse_error("Text", parsed_rule.unparsed_rule); return false; }
PartClass c = new PartClass();
c.part_type = PartEnum.Text;
c.negated = negated;
c.string_list = new List(part.Substring(negated ? 9 : 5).ToLower().Split(new char[] { rulz_item_separator }, StringSplitOptions.RemoveEmptyEntries));
parsed_rule.parts.Add(c);
return true;
}
// a player name can be extracted from the say text (into %t%)
private bool parse_targetplayer(ref ParsedRule parsed_rule, string part, bool negated)
{
PartClass c = new PartClass();
c.part_type = PartEnum.TargetPlayer;
c.negated = negated;
if (part.Length > 12) c.string_list.Add(part.Substring(13));
parsed_rule.parts.Add(c);
return true;
}
// RULZ VARIABLE INCR, DECR, SET and TEST conditions
private bool parse_incr(ref ParsedRule parsed_rule, string[] fragments)
{
if (fragments.Length != 2) { parse_error("Incr", parsed_rule.unparsed_rule); return false; }
PartClass c = new PartClass();
c.part_type = PartEnum.Incr;
c.negated = false;
c.string_list.Add(fragments[1].ToLower());
parsed_rule.parts.Add(c);
return true;
}
private bool parse_decr(ref ParsedRule parsed_rule, string[] fragments)
{
if (fragments.Length != 2) { parse_error("Decr", parsed_rule.unparsed_rule); return false; }
PartClass c = new PartClass();
c.part_type = PartEnum.Decr;
c.negated = false;
c.string_list.Add(fragments[1].ToLower());
parsed_rule.parts.Add(c);
return true;
}
private bool parse_set(ref ParsedRule parsed_rule, string[] fragments)
{
if (fragments.Length < 3)
{
parse_error("Set", parsed_rule.unparsed_rule);
return false;
}
PartClass p = new PartClass();
p.part_type = PartEnum.Set;
p.negated = false;
p.string_list.Add(fragments[1]); // string_list[0] = var_name
string value = "";
for (int i = 2; i < fragments.Length; i++) value += fragments[i];
p.string_list.Add(value); //string_list[1] = value to assign
p.has_count = has_a_count(p);
parsed_rule.parts.Add(p);
return true;
}
private bool parse_test(ref ParsedRule parsed_rule, string part, bool negated)
{
string[] fragments_in = quoted_split(part);
WriteConsole("QUOTED RETURNED:" + String.Join(", ", fragments_in));
string[] fragments;
// if "Not If" then shift fragments left
List fragment_list = new List();
if (negated)
{
for (int i = 1; i < fragments_in.Length; i++)
{
fragment_list.Add(fragments_in[i]);
}
fragments = fragment_list.ToArray();
}
else
fragments = fragments_in;
// OK we got to here with the fragments being "If", val1, condition, val2
if (new List(fragments).RemoveAll(s => check4logical(s))*4 + 4 > fragments.Length)
{
parse_error("Bad If condition", parsed_rule.unparsed_rule);
return false;
}
PartClass p = new PartClass();
p.part_type = PartEnum.Test;
p.negated = negated;
string val1 = "";
for(int i = 1; i < fragments.Length; i++)
{
try
{
if (check4condition(fragments[i]) || check4logical(fragments[i]) && !check4condition(fragments[i - 1]) && !check4logical(fragments[i - 1]) && !check4condition(fragments[i + 1]) && !check4logical(fragments[i + 1]))
{
p.string_list.Add(val1);
p.string_list.Add(fragments[i]);
val1 = "";
continue;
} else if (check4condition(fragments[i]) || check4logical(fragments[i])) {
parse_error("Bad If conditional operator. Use quotes for keywords like \"AND\" or \"==\"", parsed_rule.unparsed_rule);
return false;
}
} catch(IndexOutOfRangeException e)
{
parse_error("Bad If condition", parsed_rule.unparsed_rule);
}
val1 += fragments[i];
}
p.string_list.Add(val1);
p.has_count = has_a_count(p);
parsed_rule.parts.Add(p);
WriteConsole("AFTER RETURNED:" + String.Join(", ", p.string_list.ToArray()));
return true;
}
private bool parse_test_operand(string[] fragments, int index_afteroperand, ref List string_list)
{
string val1 = "";
int conditional_index = index_afteroperand;
for(int i = index_afteroperand; i < fragments.Length; i++)
{
if (check4condition(fragments[i]))
{
string_list.Add(val1);
string_list.Add(fragments[i]);
conditional_index = i;
val1 = "";
break;
}
val1 += fragments[i];
}
if(conditional_index == index_afteroperand)
{
return false;
}
if (check4logical(fragments[conditional_index + 1])) return false;
for (int i = conditional_index+1; i < fragments.Length; i++)
{
if(check4logical(fragments[i]))
{
string_list.Add(val1);
string_list.Add(fragments[i]);
if(!parse_test_operand(fragments, i + 1, ref string_list)) return false;
break;
}
val1 += fragments[i];
}
return true;
}
private bool check4condition(string s)
{
return s == "=" ||
s == "!=" ||
s == "==" ||
s == "<>" ||
s == ">" ||
s == "<" ||
s == ">=" ||
s == "=>" ||
s == "<=" ||
s == "=<" ||
s.ToLower() == "contains" ||
s.ToLower() == "regx" ||
s.ToLower() == "word";
}
private static bool check4logical(string s)
{
return s == "AND" || s == "OR" || s == "~AND" || s == "~OR";
}
#endregion
#region Callbacks for misc. server EVENTS (player joins/moves, list players, round end etc)
//********************************************************************************************
//********************************************************************************************
//************************* various other events from procon handled here
//********************************************************************************************
//********************************************************************************************
public override void OnPlayerTeamChange(string player_name, int iTeamID, int iSquadID)
{
// EVENT EXCEPTION BLOCK:
try
{
players.team_move(player_name, iTeamID.ToString(), iSquadID.ToString());
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerTeamChange");
PrintException(ex);
}
}
public override void OnPlayerSquadChange(string player_name, int iTeamID, int iSquadID)
{
// EVENT EXCEPTION BLOCK:
try
{
players.team_move(player_name, iTeamID.ToString(), iSquadID.ToString());
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerSquadChange");
PrintException(ex);
}
}
public override void OnPlayerJoin(string player_name)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnPlayerJoin******************************" +
player_name);
if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
players.new_player(player_name);
// update the admin list with this player name if necessary
admins_add(player_name);
// scan for any "On Join" rulz
scan_rules(TriggerEnum.Join, player_name,
new Dictionary(), null, null, null);
if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerJoin");
PrintException(ex);
}
}
public override void OnPlayerLeft(CPlayerInfo cpiPlayer)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnPlayerLeft******************************" +
cpiPlayer.SoldierName);
scan_rules(TriggerEnum.Leave, cpiPlayer.SoldierName,
new Dictionary(), null, null, null);
if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
//Removes left player from all lists
players.remove(cpiPlayer.SoldierName);
spawn_counts.zero_player(cpiPlayer.SoldierName);
admins_remove(cpiPlayer.SoldierName);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerLeft");
PrintException(ex);
}
}
public override void OnPunkbusterPlayerInfo(CPunkbusterInfo cpbiPlayer)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnPunkbusterPlayerInfo******************************" +
cpbiPlayer.SoldierName);
players.update(cpbiPlayer); // add pb_guid and ip
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPunkbusterPlayerInfo");
PrintException(ex);
}
}
public override void OnListPlayers(List lstPlayers, CPlayerSubset cpsSubset)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnListPlayers******************************");
// if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
// if 'admin.listPlayers all' then do full update of players list and return
if (cpsSubset.Subset == CPlayerSubset.PlayerSubsetType.All)
{
players.pre_scrub(); // reset all 'updated' flags to false
admins_reset(); // empty list of currently logged on admins
foreach (CPlayerInfo cp_info in lstPlayers)
{
players.update(cp_info);
// add this player to list of logged-on admins if required
admins_add(cp_info.SoldierName);
// create/update a score variable for each player
string var_name = "%server_score[" + cp_info.SoldierName + "]%";
rulz_vars.set_value(null, var_name, cp_info.Score.ToString(), null);
}
players.scrub(); // remove all players that were not updated
}
if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnListPlayers");
PrintException(ex);
}
}
public override void OnReservedSlotsList(List lstSoldierNames)
{
// EVENT EXCEPTION BLOCK:
try
{
this.reserved_slot_players = lstSoldierNames;
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnReservedSlotsList");
PrintException(ex);
}
}
// test_round_over will trigger On RoundOver rulz IF game is earlier than BF4
// OR all three BF4 round over events have fired
// i.e. RoundOver, RoundOverPlayers, RoundOverTeamScores
public void test_round_over()
{
// EVENT EXCEPTION BLOCK:
try
{
if (game_id == GameIdEnum.BF3 ||
game_id == GameIdEnum.BFBC2 ||
game_id == GameIdEnum.MoH ||
++round_over_event_count == 3)
{
// check rules for On Round trigger
scan_rules(TriggerEnum.RoundOver, null, new Dictionary(), null, null, null);
round_over_event_count = 0;
}
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in test_round_over()");
PrintException(ex);
}
}
public override void OnRoundOver(int iWinningTeamID)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnRoundOver******************************");
// trigger On RoundOver rulz if all round over events complete now
test_round_over();
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnRoundOver");
PrintException(ex);
}
}
public override void OnRoundOverPlayers(List players)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnRoundOverPlayers******************************");
// trigger On RoundOver rulz if all round over events complete now
test_round_over();
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnRoundOverPlayers");
PrintException(ex);
}
}
public override void OnRoundOverTeamScores(List scores)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: ********************OnRoundOverTeamScores******************************");
// trigger On RoundOver rulz if all round over events complete now
test_round_over();
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnRoundOverTeamScores");
PrintException(ex);
}
}
// BFBC2 only
// on round start we reset some of the ProconRulz counts
// (e.g. # times players have triggered rules)
// the only subtlety is the rules haven't changed so we can keep those,
// and hence keep the list of 'watched' items
// the 'Rates' counts (Rate 5 10) as of v32 continue past round/map change transitions...
public override void OnLoadingLevel(string strMapFileName, int roundsPlayed, int roundsTotal)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: *******************OnLoadingLevel*****************************" +
strMapFileName);
rulz_enable = true;
current_map = this.GetMapByFilename(strMapFileName);
current_map_mode = current_map.GameMode;
// empty the rulz vars
rulz_vars.reset();
zero_counts();
clear_blocks();
// remove players no longer on the server from the rates counts
scrub_rates(players.list_players());
// exec listPlayers to initialise players global
ExecuteCommand("procon.protected.send", "admin.listPlayers", "all");
load_reserved_slot_players();
// initialise ProconRulz On Init vars
OnInit();
// check rules for On Round trigger
scan_rules(TriggerEnum.Round, null, new Dictionary(), null, null, null);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnLoadingLevel");
PrintException(ex);
}
}
// BF3 only
// on round start we reset some of the ProconRulz counts
// (e.g. # times players have triggered rules)
// the only subtlety is the rules haven't changed so we can keep those,
// and hence keep the list of 'watched' items
// the 'Rates' counts (Rate 5 10) as of v32 continue past round/map change transitions...
public void OnLevelLoaded(string strMapFileName, string strMapMode, int roundsPlayed, int roundsTotal)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: *******************OnLevelLoaded*****************************" +
strMapFileName + " " + strMapMode + " " + roundsPlayed + "/" + roundsTotal);
// initialize counter for 3 BF4 RoundOver events (RoundOver, RoundOverPlayers, RoundOverTeamScores)
round_over_event_count = 0;
rulz_enable = true;
current_map = this.GetMapByFilename(strMapFileName);
current_map_mode = strMapMode;
zero_counts();
clear_blocks();
// empty the rulz vars
rulz_vars.reset();
// remove players no longer on the server from the rates counts
scrub_rates(players.list_players());
// exec listPlayers to initialise players global
ExecuteCommand("procon.protected.send", "admin.listPlayers", "all");
load_reserved_slot_players();
// initialise ProconRulz On Init vars
OnInit();
// check rules for On Round trigger
scan_rules(TriggerEnum.Round, null, new Dictionary(), null, null, null);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnLevelLoaded");
PrintException(ex);
}
}
public void OnCurrentLevel(string strMapFileName)
{
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: *******************OnCurrentLevel*****************************" +
strMapFileName);
current_map = this.GetMapByFilename(strMapFileName);
current_map_mode = current_map.GameMode;
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnCurrentLevel");
PrintException(ex);
}
}
public override void OnGlobalChat(string strSpeaker, string strMessage)
{
// EVENT EXCEPTION BLOCK:
try
{
say_rulz(strSpeaker, strMessage, "global");
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnGlobalChat");
PrintException(ex);
}
}
public override void OnTeamChat(string strSpeaker, string strMessage, int iTeamID)
{
// EVENT EXCEPTION BLOCK:
try
{
say_rulz(strSpeaker, strMessage, "team");
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnTeamChat");
PrintException(ex);
}
}
public override void OnSquadChat(string strSpeaker, string strMessage, int iTeamID, int iSquadID)
{
// EVENT EXCEPTION BLOCK:
try
{
say_rulz(strSpeaker, strMessage, "squad");
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnSquadChat");
PrintException(ex);
}
}
public virtual void OnServerInfo(CServerInfo serverInfo) {
List scores = new List(); // number of tickets remaining per team
// EVENT EXCEPTION BLOCK:
try
{
WriteDebugInfo("ProconRulz: *******************OnServerInfo*****************************");
current_map = this.GetMapByFilename(serverInfo.Map);
current_map_mode = serverInfo.GameMode;
if (serverInfo.TeamScores != null)
{
// set up team score variables %server_team_score[1]%, %server_team_score[2]% ...
foreach (TeamScore t in serverInfo.TeamScores)
{
if (t.TeamID == null || t.Score == null)
{
WriteConsole(String.Format("ProconRulz: OnServerInfo TeamID,Score error [{0}][{1}]", t.TeamID, t.Score));
break;
}
string var_name = "%server_team_score[" + t.TeamID.ToString() + "]%";
rulz_vars.set_value(null, var_name, t.Score.ToString(), null);
}
}
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnServerInfo");
PrintException(ex);
}
}
#endregion
#region Callbacks for SPAWN and KILL events - this is where most of the magic happens
//**********************************************************************************************
//**********************************************************************************************
// PROCESS VARIOUS PROCON 'EVENT' PROCEDURES - e.g. OnPlayerSpawned(), OnPlayerKilled()
//**********************************************************************************************
//**********************************************************************************************
//********************************************************************************************
// this procedure gets called when every player SPAWNS
// so at that point we will scan the rules and decide whether to apply any actions
//********************************************************************************************
public override void OnPlayerSpawned(string player_name, Inventory inv)
{
// EVENT EXCEPTION BLOCK:
try
{
// debug stuff
string blocks = "";
if (player_blocks.ContainsKey(player_name))
blocks = String.Join("/", player_blocks[player_name].ToArray());
WriteDebugInfo("********************OnPlayerSpawned***************************" +
player_name);
WriteDebugInfo(String.Format("ProconRulz: OnPlayerSpawned [{0}] with blocks [{1}]",
player_name, blocks));
//end debug
// create structure to hold substitution keywords and values
Dictionary keywords = new Dictionary();
// remove this player from the spawn item lists for now
spawn_counts.zero_player(player_name);
remove_blocks(player_name); // remove any player_blocks entries
// BF bug workaround -
// check to see if player can do damage Shotgun before saving spec sp-shotgun_s
bool has_damage_shotgun = false;
string player_team_id = players.team_id(player_name);
WriteDebugInfo(String.Format("ProconRulz: OnPlayerSpawned [{0}] team_id {1}",
player_name, player_team_id));
// add player to kit spawned list, if kit is 'watched'
spawn_counts.add(item_key(inv.Kit), player_team_id, player_name);
player_kit[player_name] = item_key(inv.Kit); // record kit that this player spawned with
WriteDebugInfo(String.Format("ProconRulz: OnPlayerSpawned [{0}] kit {1}",
player_name, player_kit[player_name]));
List damages = new List();
List weapon_keys = new List();
List weapon_names = new List();
foreach (Weapon w in inv.Weapons)
{
// add playername to watch list for weapon, if it's being watched
spawn_counts.add(item_key(w), player_team_id, player_name);
// add playername to watch list for weapon damage, if it's being watched
spawn_counts.add(item_key(w.Damage), player_team_id, player_name);
// BF bug workaround - remember if player can do damage shotgun
if (item_key(w.Damage).ToLower() == "shotgun") has_damage_shotgun = true;
// build 'weapons' and damages string lists for debug printout and subst variables
weapon_keys.Add(w.Name);
weapon_names.Add(weapon_desc(w.Name));
damages.Add(item_key(w.Damage));
} // end looping through weapons in inventory
// store %wk% subst var (weapon keys)
if (weapon_keys.Count > 0)
keywords.Add(SubstEnum.WeaponKey, string.Join(", ", weapon_keys.ToArray()));
else
keywords.Add(SubstEnum.WeaponKey, "No weapon key");
// store %w% subst var (weapon names)
if (weapon_names.Count > 0)
keywords.Add(SubstEnum.Weapon, string.Join(", ", weapon_names.ToArray()));
else
keywords.Add(SubstEnum.Weapon, "No weapon");
// store %d% subst var (damages)
if (damages.Count > 0)
keywords.Add(SubstEnum.Damage, string.Join(", ", damages.ToArray()));
else
keywords.Add(SubstEnum.Damage, "No damage");
WriteDebugInfo(String.Format("ProconRulz: OnPlayerSpawned [{0}] weapons [{1}]",
player_name, keywords[SubstEnum.Weapon]));
List spec_keys = new List();
List spec_names = new List();
foreach (Specialization s in inv.Specializations)
{
// condition is a BF bug workaround
// Add this Spec IF player has a weapon that does damage Shotgun,
// OR if the spec is not 12-Gauge Sabot Rounds
if (has_damage_shotgun || (!(item_key(s).ToLower() == "sp_shotgun_s")))
spawn_counts.add(item_key(s), player_team_id, player_name);
spec_keys.Add(item_key(s));
spec_names.Add(spec_desc(s));
} // end looping through specs in inventory
// store %speck% subst var (spec keys)
if (spec_keys.Count > 0)
keywords.Add(SubstEnum.SpecKey, string.Join(", ", spec_keys.ToArray()));
else
keywords.Add(SubstEnum.SpecKey, "No spec key");
// store %spec% subst var (specializations)
if (spec_names.Count > 0)
keywords.Add(SubstEnum.Spec, string.Join(", ", spec_names.ToArray()));
else
keywords.Add(SubstEnum.Spec, "No spec");
WriteDebugInfo(
String.Format("^bProconRulz: [{0}] {1} spawned. Kit {2}. Weapons [{3}]. Specs [{4}]. Damages [{5}]",
team_name(player_team_id),
player_name,
item_key(inv.Kit),
keywords[SubstEnum.Weapon],
keywords[SubstEnum.Spec],
keywords[SubstEnum.Damage]
));
// debug stuff
if (trace_rules == enumBoolYesNo.Yes) prdebug("counts");
//Check if the player carries any of the things we're looking for
scan_rules(TriggerEnum.Spawn, player_name, keywords, inv, null, null);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerSpawn");
PrintException(ex);
}
}
//********************************************************************************************
// this procedure gets called when each KILL occurs in the game
//********************************************************************************************
public override void OnPlayerKilled(Kill k)
{
// EVENT EXCEPTION BLOCK:
try
{
if (k == null)
{
WriteDebugInfo("*******************OnPlayerKilled*****************NULL KILL OBJECT");
return;
}
if (k.Killer == null)
{
WriteDebugInfo("*******************OnPlayerKilled***************NULL KILLER OBJECT");
return;
}
CPlayerInfo Killer = k.Killer;
CPlayerInfo Victim = k.Victim;
string player_name = Killer.SoldierName;
string victim_name = Victim.SoldierName;
// cache the player info in case we want the GUID
if (player_name != null && player_name != "") player_info[player_name] = Killer;
if (victim_name != null && victim_name != "") player_info[victim_name] = Victim;
WriteDebugInfo("*******************OnPlayerKilled*************************"+player_name);
if (Victim == null)
{
WriteDebugInfo(String.Format("NULL VICTIM on this kill by {0}", player_name));
return;
}
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled [{0}] [{1}]",
player_name, victim_name));
string weapon_key = k.DamageType;
if (weapon_key == "" || weapon_key == null) weapon_key = "No weapon key";
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled [{0}] weapon_key [{1}]",
player_name, weapon_key));
Weapon weapon_used;
try
{
if (weapon_key == null) weapon_used = null;
else weapon_used = weaponDefines[weapon_key];
}
catch { weapon_used = null; }
string weapon_descr = weapon_desc(weapon_key);
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled [{0}] weapon_descr [{1}]",
player_name, weapon_descr));
string weapon_kit;
try
{
if (weapon_used == null) weapon_kit = "No weapon kit";
else weapon_kit = item_key(weapon_used.KitRestriction);
}
catch { weapon_kit = "No weapon kit"; }
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled [{0}] kit [{1}]",
player_name, weapon_kit));
string damage;
try
{
damage = item_key(weapon_used.Damage);
}
catch { damage = "No damage key"; }
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled [{0}] damage [{1}]",
player_name, damage));
add_kill_count(player_name, weapon_key);
add_kill_count(player_name, damage);
add_kill_count(player_name, weapon_kit);
// debug
string killer_counts = "";
if (kill_counts.ContainsKey(player_name))
foreach (string item_name in kill_counts[player_name].Keys)
{
List item_list = new List();
item_list.Add(item_name);
killer_counts += item_name + "(" +
count_kill_items(player_name, item_list).ToString() + ") ";
}
else killer_counts = "0 kill counts";
WriteDebugInfo(
String.Format("^bProconRulz: [{0} {1} [{2}]] killed [{3}] with [{4}], damage {5}, range {6}",
weapon_kit, player_name, killer_counts,
victim_name, weapon_descr, damage, (Int32)k.Distance));
//end debug
// clear the dead soldier out of the 'counts' if it's first come first served
// this will open up an opportunity for someone else to spawn with this players items
if (reservationMode == ReserveItemEnum.Player_loses_item_when_dead)
{
spawn_counts.zero_player(Victim.SoldierName);
}
TriggerEnum kill_type = TriggerEnum.Kill;
string blocked_item = "";
if (k.IsSuicide || player_name == null || player_name == "") // BF3 reports no killer with SoldierCollision
{
kill_type = TriggerEnum.Suicide;
// - this is just for testing the suicide data
WriteDebugInfo("Suicide info: " +
"k.IsSuicide=" + (k.IsSuicide ? "true" : "false") +
", player_name=" + (player_name == null ? "null" : "\"" + player_name + "\"") +
", victim_name=" + (victim_name == null ? "null" : "\"" + victim_name + "\"") +
", weapon_key=" + weapon_key
);
if (player_name == null || player_name == "") player_name = victim_name;
}
else if (test_block(player_name, weapon_key))
{
kill_type = TriggerEnum.PlayerBlock;
blocked_item = weapon_descr;
WriteDebugInfo(String.Format("ProconRulz: PlayerBlock [{0}] with weapon [{1}]",
player_name, blocked_item));
}
else if (test_block(player_name, weapon_kit))
{
kill_type = TriggerEnum.PlayerBlock;
blocked_item = weapon_kit;
WriteDebugInfo(String.Format("ProconRulz: PlayerBlock [{0}] with kit [{1}]",
player_name, blocked_item));
}
else if (test_block(player_name, damage))
{
kill_type = TriggerEnum.PlayerBlock;
blocked_item = damage;
WriteDebugInfo(String.Format("ProconRulz: PlayerBlock [{0}] with damage [{1}]",
player_name, blocked_item));
}
else if (Killer.TeamID == Victim.TeamID) kill_type = TriggerEnum.TeamKill;
WriteDebugInfo(String.Format("ProconRulz: OnPlayerKilled for [{0}] is Event {1}",
player_name,
Enum.GetName(typeof(TriggerEnum), kill_type)));
// now we do the main work of scanning the rules for this KILL
scan_rules(kill_type, player_name, new Dictionary(),
null, k, blocked_item);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in OnPlayerKilled");
PrintException(ex);
}
}
#endregion
#region Step through the rules, looking for trigger that matches the event
//**********************************************************************************************
//**********************************************************************************************
// CHECK THE RULES to see if we should kill, kick, say
//**********************************************************************************************
//**********************************************************************************************
// here is where we scan the rules after a player has joined, spawned or a kill has occurred
private void scan_rules(TriggerEnum trigger, string name,
Dictionary keywords,
Inventory inv, Kill k, string item)
{
WriteDebugInfo(String.Format("ProconRulz: Scan_rules[{0}] with Event {1}",
name,
Enum.GetName(typeof(TriggerEnum), trigger)));
// don't do anything if rulz_enable has been set to false
if (!rulz_enable) return;
// CATCH EXCEPTIONS
try
{
// initial population of the 'keywords' dictionary
assign_initial_keywords(name, ref keywords);
// loop through the rules
foreach (ParsedRule rule in parsed_rules)
{
// skip comments
if (rule.comment) continue;
if (rule.trigger == trigger)
{
WriteDebugInfo(String.Format("ProconRulz: scan_rules[{0}] [{1}]",
name, rule.unparsed_rule));
if (process_rule(trigger, rule, name, ref keywords, inv, k, item))
{
WriteDebugInfo(String.Format("ProconRulz: scan_rules[{0}] [{1}] FIRED",
name, rule.unparsed_rule));
break; // break if any rule fires
}
}
// else WriteDebugInfo(String.Format("ProconRulz: scan_rules[{0}] skipped", name));
} // end looping through the rules
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in scan_rules");
PrintException(ex);
}
}
// test a rule (we have already confirmed Spawn or Kill trigger is true)
// will return 'true' if the rule is applied
private bool process_rule(TriggerEnum trigger, ParsedRule rule, string player_name,
ref Dictionary keywords,
Inventory inv, Kill k, string item)
{
WriteDebugInfo(String.Format("ProconRulz: process_rule[{0}] with event {1}",
player_name,
Enum.GetName(typeof(TriggerEnum), trigger)));
// CATCH EXCEPTIONS
try
{
List parts = new List(rule.parts);
if (trigger == TriggerEnum.Say)
{
Match match = Regex.Match(item, @"\[(.*?)\](.*)");
keywords[SubstEnum.Text] = match.Groups[2].Value;
keywords[SubstEnum.Channel] = match.Groups[1].Value;
item = match.Groups[2].Value;
}
if (trigger == TriggerEnum.ServerSay)
{
player_name = null;
keywords[SubstEnum.Text] = item;
}
// Populate the Counts AS IF THIS RULE SUCCEEDED so conditions can use them
keywords[SubstEnum.ServerCount] = count_server_rule(rule.id).ToString() + 1;
keywords[SubstEnum.Count] = count_rule(player_name, rule.id).ToString() + 1;
keywords[SubstEnum.TeamCount] = count_team_rule(players.team_id(player_name), rule.id).ToString() + 1;
// populate the 'keywords' dictionary
assign_keywords(trigger, rule, player_name, ref keywords, inv, k, item);
if (!process_parts(rule, parts, player_name, ref keywords, k, item))
{
WriteDebugInfo(String.Format("ProconRulz: process_rule[{0}] in rule [{1}] tests NEGATIVE",
player_name, rule.unparsed_rule));
return false;
}
WriteDebugInfo(String.Format("ProconRulz: process_rule[{0}] in rule [{1}] all conditions OK",
player_name, rule.unparsed_rule));
// return 'true' to quit rulz checks after this rule
// if rule contains End, Kill, Kick, TempBan, Ban unless it contains Continue.
return end_rulz(parts);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in test_rule_and_exec(" +
rule.unparsed_rule + ")");
PrintException(ex);
return false;
}
}
private bool process_parts(ParsedRule rule, List parts, string player_name,
ref Dictionary keywords,
Kill k, string item)
{
PartClass current_part = null;
try
{
bool playercount_updated = false; // we only update PlayerCount etc ONCE either after a successful
// PlayerCount, or before the use of %c% etc
// check each of the PARTS.
// for each part in the rule, call process_part()
foreach (PartClass p in parts)
{
current_part = p; // so we can display the part that caused exception if needed
// see if we should update PlayerCount etc here
// rule can by NULL if parts is a list of TargetActions
if (rule != null && !playercount_updated && p.has_count)
{
update_counts(player_name, rule.id, ref keywords);
playercount_updated = true;
}
// HERE IS WHERE WE LEAVE THE PROC AND RETURN FALSE IF A CONDITION FAILS
if (!process_part(rule, p,
player_name, k, item, ref keywords))
return false;
WriteDebugInfo(String.Format("ProconRulz: process_parts [{0}] OK", player_name));
}
return true;
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in process_parts (ProconRulz will continue)");
WriteConsole("ProconRulz: Rule was: " + rule.unparsed_rule);
if (current_part != null)
{
WriteConsole("ProconRulz: Rule part was: " + current_part.ToString());
}
else
{
WriteConsole("ProconRulz: Rule part was null");
}
PrintException(ex);
return false;
}
}
// return 'true' to end rulz if rule contains End, Kill, Kick, TempBan, Ban unless it contains Continue.
bool end_rulz(List parts)
{
foreach (PartClass p in parts)
{
if (p.part_type == PartEnum.Continue) return false;
if (p.part_type == PartEnum.End) return true;
}
foreach (PartClass p in parts)
{
if (p.part_type == PartEnum.Kill ||
p.part_type == PartEnum.Kick ||
p.part_type == PartEnum.TempBan ||
p.part_type == PartEnum.Ban ||
p.part_type == PartEnum.PBBan ||
p.part_type == PartEnum.PBKick
) return true;
}
return false;
}
// return true if this part refers to a Count
bool has_a_count(PartClass p)
{
if (p.part_type == PartEnum.PlayerCount ||
p.part_type == PartEnum.TeamCount ||
p.part_type == PartEnum.ServerCount) return true;
foreach (string s in p.string_list)
{
foreach (string k in subst_keys[SubstEnum.Count])
{
if (s.Contains(k)) return true;
}
foreach (string k in subst_keys[SubstEnum.TeamCount])
{
if (s.Contains(k)) return true;
}
foreach (string k in subst_keys[SubstEnum.ServerCount])
{
if (s.Contains(k)) return true;
}
}
return false;
}
private void update_counts(string player_name, int rule_id,ref Dictionarykeywords)
{
// increment the 'count' for this player for this rule
add_rule_count(player_name, rule_id);
// Now populate the Counts properly for the ACTIONS
keywords[SubstEnum.ServerCount] = count_server_rule(rule_id).ToString();
keywords[SubstEnum.Count] = count_rule(player_name, rule_id).ToString();
keywords[SubstEnum.TeamCount] = count_team_rule(keywords[SubstEnum.PlayerTeamKey], rule_id).ToString();
}
private void assign_initial_keywords(string player_name, ref Dictionary keywords)
{
keywords[SubstEnum.Date] = DateTime.Now.ToString("yyyy_MM_dd");
keywords[SubstEnum.Hhmmss] = DateTime.Now.ToString("HH:mm:ss");
keywords[SubstEnum.Seconds] = Math.Floor(DateTime.Now.Subtract(new DateTime(2012, 1, 1, 0, 0, 0)).TotalSeconds).ToString();
keywords[SubstEnum.Random] = new Random(DateTime.Now.Millisecond + DateTime.Now.DayOfYear).NextDouble().ToString();
// put the map name and mode into the subst keys
if (current_map != null)
{
keywords[SubstEnum.Map] = current_map.PublicLevelName;
keywords[SubstEnum.MapMode] = current_map_mode;
}
keywords[SubstEnum.Teamsize] = players.min_teamsize().ToString();
keywords[SubstEnum.Teamsize1] = players.teamsize("1").ToString();
keywords[SubstEnum.Teamsize2] = players.teamsize("2").ToString();
//debug -- these check values say where the exception was...
int check = 1;
if (player_name != null)
{
try
{
check = 2;
keywords[SubstEnum.Player] = player_name;
check = 3;
keywords[SubstEnum.Ping] = players.ping(player_name).ToString();
check = 4;
keywords[SubstEnum.EA_GUID] = players.ea_guid(player_name);
check = 5;
keywords[SubstEnum.PB_GUID] = players.pb_guid(player_name);
check = 6;
keywords[SubstEnum.IP] = players.ip(player_name);
check = 7;
string player_team_id = players.team_id(player_name);
check = 8;
string player_squad_id = players.squad_id(player_name);
check = 9;
keywords[SubstEnum.PlayerTeamsize] = players.teamsize(player_team_id).ToString();
check = 10;
keywords[SubstEnum.PlayerTeam] = team_name(player_team_id);
check = 11;
keywords[SubstEnum.PlayerSquad] = squad_name(player_squad_id);
check = 12;
keywords[SubstEnum.PlayerTeamKey] = player_team_id;
check = 13;
keywords[SubstEnum.PlayerSquadKey] = player_squad_id;
check = 14;
keywords[SubstEnum.PlayerCountry] = players.cname(player_name);
check = 15;
keywords[SubstEnum.PlayerCountryKey] = players.ckey(player_name);
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in assign_initial_keywords #1 check("+check.ToString()+")");
PrintException(ex);
return;
}
}
else
{
keywords[SubstEnum.Player] = "";
}
}
private void assign_keywords(TriggerEnum trigger, ParsedRule rulex, string player_name,
ref Dictionary keywords,
Inventory inv, Kill k, string item)
{
// HERE's WHERE WE UPDATE THE SUBST KEYWORDS (and conditions can as well,
// e.g. TargetPlayer)
// this would be more efficient if I only updated the keywords that are
// actually used in the rulz
// update player count etc for this rule (for use of %c% parameter
// or subsequent Count condition)
if (trigger == TriggerEnum.Spawn)
{
keywords[SubstEnum.Kit] = kit_desc(item_key(inv.Kit));
keywords[SubstEnum.KitKey] = item_key(inv.Kit);
// keywords[Weaponkey, Weapon, Damage, SpecKey, Spec] all set in OnPlaeyrSpawned
}
else if (trigger == TriggerEnum.Kill ||
trigger == TriggerEnum.TeamKill ||
trigger == TriggerEnum.Suicide ||
trigger == TriggerEnum.PlayerBlock
)
{
try
{
// we're in a 'kill' type event here
// as far as I can tell, 'k.DamageType' usually contains
// weapon key, but sometimes can be null
// with BF3, k.Killer.SoldierName can be empty (null or ""?)
if (k == null) return;
string victim_name = (k.Victim == null) ? "No victim" : k.Victim.SoldierName;
keywords[SubstEnum.Victim] = victim_name;
string weapon_key = k.DamageType;
Weapon weapon_used;
try
{
if (weapon_key == null) weapon_used = null;
else weapon_used = weaponDefines[weapon_key];
}
catch { weapon_used = null; }
string weapon_descr = weapon_desc(weapon_key);
string damage;
try
{
damage = (weapon_used == null) ? "No damage key" : item_key(weapon_used.Damage);
}
catch { damage = "No damage key"; }
keywords[SubstEnum.Weapon] = weapon_descr;
keywords[SubstEnum.WeaponKey] = weapon_key;
keywords[SubstEnum.KitKey] = spawned_kit(player_name);
keywords[SubstEnum.Kit] = kit_desc(spawned_kit(player_name));
keywords[SubstEnum.VictimKit] = kit_desc(spawned_kit(victim_name));
keywords[SubstEnum.VictimTeamKey] = players.team_id(victim_name);
keywords[SubstEnum.VictimTeam] = team_name(players.team_id(victim_name));
keywords[SubstEnum.VictimCountry] = players.cname(victim_name);
keywords[SubstEnum.VictimCountryKey] = players.ckey(victim_name);
keywords[SubstEnum.Damage] = damage;
keywords[SubstEnum.Range] = k.Distance.ToString("0.0");
keywords[SubstEnum.Headshot] = k.Headshot ? "Headshot" : "";
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in assign_keywords #2");
PrintException(ex);
return;
}
}
if (trigger == TriggerEnum.PlayerBlock)
{
keywords.Add(SubstEnum.BlockedItem, item);
WriteDebugInfo(String.Format("ProconRulz: test_rule[{0}] is PlayerBlock event for [{1}] OK",
player_name, item));
}
}
#endregion
#region Process a single 'part' of rulz i.e. CONDITION or ACTION
// check a condition (e.g. "Kit Recon 2") in the current rule
// rule.trigger is already confirmed to be the current event (e.g. Kill, Spawn)
private bool process_part(ParsedRule rule, PartClass p,
string player_name,
Kill k, string msg,
ref Dictionary keywords)
{
// CATCH EXCEPTIONS
try
{
string not = p.negated ? "Not " : "";
bool return_val = false;
string player_team_id = "-1";
if (keywords.ContainsKey(SubstEnum.PlayerTeamKey))
{
player_team_id = keywords[SubstEnum.PlayerTeamKey];
}
switch (p.part_type)
{
case PartEnum.Headshot:
// test "Headshot"
return_val = p.negated ? !k.Headshot : k.Headshot;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Headshot {2}",
player_name, not, return_val));
return return_val;
case PartEnum.Protected:
// test player os on reserved slots list
return_val = p.negated ? !protected_player(player_name) : protected_player(player_name);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Protected {2}",
player_name, not, return_val));
return return_val;
case PartEnum.Admin:
// test player is an admin
return_val = p.negated ? !is_admin(player_name) : is_admin(player_name);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Admin {2}",
player_name, not, return_val));
return return_val;
case PartEnum.Admins:
// test if any admins are currently online
return_val = p.negated ? !admins_present() : admins_present();
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Admins {2}",
player_name, not, return_val));
return return_val;
case PartEnum.Team:
// test "team attack|defend"
bool team_matches = team_match(p.string_list, player_team_id);
return_val = p.negated ? !team_matches : team_matches;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Actual team {2} versus {3} {4}",
player_name, not, team_key(player_team_id), keys_join(p.string_list), return_val));
return return_val;
case PartEnum.Ping:
// test "Ping N"
int current_ping = players.ping(player_name);
return_val = p.negated ? current_ping < p.int1 : current_ping >= p.int1;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Ping {2} versus limit {3} {4}",
player_name, not, current_ping, p.int1, return_val));
return return_val;
case PartEnum.Teamsize:
// test "Teamsize N"
int min_teamsize = players.min_teamsize();
return_val = p.negated ? min_teamsize > p.int1 : min_teamsize <= p.int1;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Teamsize {2} versus limit {3} {4}",
player_name, not, min_teamsize, p.int1, return_val));
return return_val;
case PartEnum.Map:
// test map name or filename contains string1
return_val = p.negated ? !map_match(p.string_list) : map_match(p.string_list);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Actual map {2} versus {3} {4}",
player_name, not,
current_map.PublicLevelName + " or " + current_map.FileName,
keys_join(p.string_list), return_val));
return return_val;
case PartEnum.MapMode:
// test "mapmode rush|conquest"
return_val = p.negated ? !mapmode_match(p.string_list) : mapmode_match(p.string_list);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}Actual MapMode {2} versus {3} {4}",
player_name, not, current_map_mode, keys_join(p.string_list), return_val));
return return_val;
case PartEnum.Kit:
case PartEnum.Weapon:
case PartEnum.Spec:
case PartEnum.Damage:
// test "Kit Recon 2" etc
if (rule.trigger == TriggerEnum.Spawn)
// will check *player* item as well as team count (spawn)
return test_spawn_item(player_team_id, player_name, p);
else
// will also test kill item for TeamKill and Suicide
return test_kill_item(k, p);
case PartEnum.TeamKit:
case PartEnum.TeamWeapon:
case PartEnum.TeamSpec:
case PartEnum.TeamDamage:
return test_spawned_count(player_team_id, p);
case PartEnum.Range:
// test "Range > int1"
return_val = p.negated ? !(k.Distance < p.int1) : k.Distance > p.int1;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}range {2} > limit {3} {4}",
player_name, not, k.Distance, p.int1, return_val));
return return_val;
case PartEnum.Count:
case PartEnum.PlayerCount:
// check how many times PLAYER has triggered this rule
int current_count = count_rule(player_name, rule.id);
bool count_valid = current_count > p.int1;
return_val = p.negated ? !count_valid : count_valid;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}PlayerCount {2} (actual {3}) {4}",
player_name, not, p.int1, current_count, return_val));
return return_val;
case PartEnum.TeamCount:
// check how many times PLAYER'S TEAM has triggered this rule
int current_team_count = count_team_rule(players.team_id(player_name), rule.id);
bool count_team_valid = current_team_count > p.int1;
return_val = p.negated ? !count_team_valid : count_team_valid;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}TeamCount {2} (actual {3}) {4}",
player_name, not, p.int1, current_team_count, return_val));
return return_val;
case PartEnum.ServerCount:
// check how many times ALL PLAYERS have triggered this rule
int current_server_count = count_server_rule(rule.id);
bool count_server_valid = current_server_count > p.int1;
return_val = p.negated ? !count_server_valid : count_server_valid;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}ServerCount {2} (Actual {3}) {4}",
player_name, not, p.int1, current_server_count, return_val));
return return_val;
case PartEnum.Rate:
// check condition "Rate X Y" i.e. X hits on this rule in Y seconds
add_rate(player_name, rule.id);
bool rate_valid = check_rate(player_name, rule.id, p.int1, p.int2);
return p.negated ? !rate_valid : rate_valid;
case PartEnum.Text:
// check say text condition e.g. "Text ofc 4 ever,teamwork is everything"
int index = -1;
foreach (string t in p.string_list)
{
index = msg.ToLower().IndexOf(t.ToLower());
if (index >= 0 && keywords[SubstEnum.Text] != null && keywords[SubstEnum.Text].Length >= t.Length + 2)
{
// set up TargetText for TargetPlayer
keywords[SubstEnum.TargetText] = keywords[SubstEnum.Text].Substring(index + t.Length).Trim();
}
if (index >= 0) break;
}
return_val = p.negated ? index == -1 : index != -1;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}text {2} {3}",
player_name, not, keys_join(p.string_list), return_val));
return return_val;
case PartEnum.Channel:
// check say channel condition e.g. "Channel squad"
return_val = p.negated ? !p.string_list.Contains(keywords[SubstEnum.Channel]) : p.string_list.Contains(keywords[SubstEnum.Channel]);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}channel {2} {3}", player_name, not, keys_join(p.string_list), return_val));
return return_val;
case PartEnum.TargetPlayer:
// check TargetPlayer condition, i.e. can we extract a playername from the say text
// updated from v33 for find_players to return a LIST of player names
// if only ONE playername matches, then automatically add TargetConfirm to action list...
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] checking TargetPlayer[{1}]",
player_name, keywords[SubstEnum.Text]));
List player_names = new List();
if (p.string_list != null && p.string_list.Count != 0)
{
// here the 'targettext' is specified in the rule e.g. "TargetPlayer bambam"
player_names = find_players(rulz_vars.replace_vars(player_name,
replace_keys(p.string_list[0], keywords)));
}
// note only 1 playername is allowed in condition
else // because it could contain rulz_item_separator
{
// here the targettext from a previous "Text" condition will be used
// if successful, we will modify TargetText to be AFTER the playername match string
string[] t_words = null;
if (keywords.ContainsKey(SubstEnum.TargetText))
{
t_words = quoted_split(keywords[SubstEnum.TargetText]);
}
if (t_words != null && t_words.Length > 0)
{
player_names = find_players(t_words[0]);
if (keywords[SubstEnum.TargetText].Length - t_words[0].Length > 1)
{
keywords[SubstEnum.TargetText] =
keywords[SubstEnum.TargetText].Substring(t_words[0].Length + 1);
}
else
{
keywords[SubstEnum.TargetText] = "";
}
}
}
return_val = p.negated ? player_names.Count == 0 : player_names.Count == 1;
keywords[SubstEnum.Target] = player_names.Count == 0 ? "" : player_names[0];
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1}TargetPlayer {2} {3} with {4}",
player_name, not, keys_join(p.string_list), return_val, String.Join(",", player_names.ToArray())));
return return_val;
case PartEnum.Set:
// set rulz variable
rulz_vars.set_value(player_name, p.string_list[0], p.string_list[1], keywords);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] Set {1} {2}",
player_name, keys_join(p.string_list), true));
return true; // set always succeeds
case PartEnum.Incr:
rulz_vars.incr(player_name, p.string_list[0], keywords);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] Increment {1} {2}",
player_name, p.string_list[0], true));
return true; // Incr always succeeds
case PartEnum.Decr:
rulz_vars.decr(player_name, p.string_list[0], keywords);
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] Decrement {1} {2}",
player_name, p.string_list[0], true));
return true; // Decr always succeeds
case PartEnum.Test: // aka If
// test var1 compare var2 (c.string_list[0..2])
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] IF %c% is [{1}]",
player_name, keywords[SubstEnum.Count]));
return_val = rulz_vars.test(player_name, p.string_list, keywords);
if (p.negated) return_val = !return_val;
WriteDebugInfo(String.Format("ProconRulz: check_condition [{0}] {1} IF {2} {3}",
player_name, not, keys_join(p.string_list), return_val));
return return_val;
default:
take_action(player_name, p, keywords);
return true;
}
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in process_part (ProconRulz will continue...)");
WriteConsole("ProconRulz: process_part rule.unparsed_rule = " +
((rule == null) ?
"(rule=null)" :
(rule.unparsed_rule == null ? "(null)" : "[" + rule.unparsed_rule + "]")
)
);
WriteConsole("ProconRulz: process_part player_name = " +
((player_name == null) ? "(null)" :"[" + player_name + "]"));
WriteConsole("ProconRulz: process_part p.part_type = " +
((p == null) ?
"(p=null)" :
(p.part_type == null ? "(null)" : p.part_type.ToString())
)
);
WriteConsole("ProconRulz: process_part k.Killer.SoldierName = " +
((k == null) ?
"(k=null)" :
((k.Killer == null) ?
"(k.Killer=null)" :
((k.Killer.SoldierName==null) ? "(null)" : ("["+k.Killer.SoldierName+"]"))
)
)
);
PrintException(ex);
return false;
}
}
// test for an item at SPAWN
// not_test is added to do a "Not Kit Recon" type test
private bool test_spawn_item(string team_id, string player_name, PartClass c)
{
// e.g. "Kit Recon 2" => c.part_type = Kit, c.string1 = "Recon", c.int1 = 2
bool found = spawn_counts.has_item(c.string_list, player_name);
WriteDebugInfo(String.Format("ProconRulz: Test spawn item [KIT {0}] {1}",
keys_join(c.string_list), found ? "Found" : "Not Found"));
// if we have NOT found the item then return FALSE for a regular condition,
// or TRUE for a 'Not' condition:
if (!found) return c.negated;
// item found
// if no count specified in condition then we can return now
// (success for normal rule, failure for 'Not' rule)
if (c.int1 == 0) return !c.negated;
// proceed to check item count
return test_spawned_count(team_id, c);
}
// test for an item on a KILL
private bool test_kill_item(Kill k, PartClass c)
{
try
{
if (k == null) return false;
string weapon_key = k.DamageType;
if (weapon_key == "") weapon_key = null;
Weapon weapon_used;
try
{
if (weapon_key == null) weapon_used = null;
else weapon_used = weaponDefines[weapon_key];
}
catch { weapon_used = null; }
string weapon_descr = weapon_desc(weapon_key);
string weapon_kit;
try
{
if (weapon_used == null) weapon_kit = "No weapon kit";
else weapon_kit = item_key(weapon_used.KitRestriction);
}
catch { weapon_kit = "No weapon kit"; }
string damage;
try
{
damage = item_key(weapon_used.Damage);
}
catch { damage = "No damage key"; }
switch (c.part_type)
{
case PartEnum.Weapon:
WriteDebugInfo(String.Format("ProconRulz: Test kill item [WEAPON {0}]",
keys_join(c.string_list)));
if (weapon_key == null) return c.negated;
if (keys_match(weapon_key, c.string_list))
{
WriteDebugInfo(String.Format("ProconRulz: Test kill item [WEAPON {0}] found",
weapon_key));
break;
}
// not found, so return false unless c.negated
return c.negated;
case PartEnum.Damage:
WriteDebugInfo(String.Format("ProconRulz: Test kill item [DAMAGE {0}]",
keys_join(c.string_list)));
if (keys_match(damage, c.string_list))
{
WriteDebugInfo(String.Format("ProconRulz: Test kill item [DAMAGE {0}] found",
damage));
break;
}
return c.negated;
case PartEnum.Kit:
WriteDebugInfo(String.Format("ProconRulz: Test kill item [KIT {0}]", keys_join(c.string_list)));
string test_kit = weapon_kit;
// either use the kit type of the weapon, or the kit the player spawned with
if (player_kit.ContainsKey(k.Killer.SoldierName))
test_kit = player_kit[k.Killer.SoldierName];
if (keys_match(test_kit, c.string_list))
{
WriteDebugInfo(String.Format("ProconRulz: Test kill item [KIT {0}] found",
test_kit));
break;
}
return c.negated;
default: // item type can be None
WriteDebugInfo(String.Format("ProconRulz: Test kill item [ignored] OK"));
return true;
} // end switch on item type
bool success;
if (c.int1 == 0)
{
success = true;
if (c.negated) success = !success;
WriteDebugInfo(String.Format("ProconRulz: Test kill item {0}", success));
// The item is being counted, and the count is above the limit in the rule
return success;
}
// check the item_limit value
WriteDebugInfo(String.Format("ProconRulz: Test kill item [{0}] has {1}({2}) versus rule limit {3}",
k.Killer.SoldierName,
keys_join(c.string_list),
count_kill_items(k.Killer.SoldierName, c.string_list),
c.int1));
success = count_kill_items(k.Killer.SoldierName, c.string_list) > c.int1;
if (c.negated) success = !success;
return success;
}
catch (Exception ex)
{
WriteConsole("ProconRulz: recoverable exception in test_kill_item");
PrintException(ex);
return false;
}
}
// test for an item at SPAWN
// not_test is added to do a "Not Kit Recon" type test
private bool test_spawned_count(string team_id, PartClass c)
{
// e.g. "TeamKit Recon 2" => c.part_type = Kit, c.string1 = "Recon", c.int1 = 2
// check the item limit value
int count = spawn_counts.count(c.string_list, team_id);
WriteDebugInfo(String.Format("ProconRulz: Test spawn item count {0} versus limit {1}",
count, c.int1));
if (count <= c.int1) return c.negated;
return !c.negated; // i.e. spawn item above count, non-negated rule => return true
}
#endregion
#region process an ACTION
//**********************************************************************************************
//**********************************************************************************************
// EXECUTE THE ACTIONS IN THE CURRENT RULE
//**********************************************************************************************
//**********************************************************************************************
// execute the next action
void take_action(string player_name, PartClass p, Dictionary keywords)
{
WriteDebugInfo(String.Format("ProconRulz: take_action[{0}] with action '{1}{2} {3}' by '{4}'",
player_name,
p.target_action ? "TargetAction " : "",
Enum.GetName(typeof(PartEnum), p.part_type),
p.string_list[0],
keywords[SubstEnum.Player]
)
);
// if current action is 'Continue' or 'End' then do nothing
if (p.part_type == PartEnum.Continue || p.part_type == PartEnum.End) return;
// if the current action was TargetAction, add to the players list and return
if (p.target_action)
{
do_action(keywords[SubstEnum.Target], p, keywords);
return;
}
// e.g. from a rule "On Say;Text Yes;TargetConfirm" --
// do nothing (obsolete action)
if (p.part_type == PartEnum.TargetConfirm)
{
return;
}
if (p.part_type == PartEnum.PlayerBlock)
{
add_block(player_name, p.string_list[0]);
return;
}
// if the action is a "Kill", then IMMEDIATELY clear this soldier out of the spawn counts
// this will open up an opportunity for someone else to spawn with this players items
if (p.part_type == PartEnum.Kill &&
reservationMode == ReserveItemEnum.Player_loses_item_when_dead &&
!protected_player(player_name))
{
spawn_counts.zero_player(player_name);
}
do_action(player_name, p, keywords);
}
// execute the action (kill in a separate thread which can sleep if necessary)
void do_action(string target, PartClass a, Dictionary keywords)
{
//object[] parameters = (object[])state;
//string target = (string)parameters[0];
//PartClass a = (PartClass)parameters[1];
//Dictionary keywords = (Dictionary)parameters[2];
// replace all the %p% etc with player name etc
string message = replace_keys(a.string_list[0], keywords);
message = rulz_vars.replace_vars(target, message);
WriteDebugInfo(String.Format("ProconRulz: Doing action '{0} {1}' on {2}",
Enum.GetName(typeof(PartEnum), a.part_type),
message,
target ));
WhatISaid.Add(message);
switch (a.part_type)
{
case PartEnum.Say:
ExecuteCommand("procon.protected.send", "admin.say", message, "all");
ExecuteCommand("procon.protected.chat.write", message);
break;
case PartEnum.PlayerSay:
ExecuteCommand("procon.protected.send", "admin.say", message, "player", target);
ExecuteCommand("procon.protected.chat.write", String.Format("(PlayerSay {0}) ",
target) + message);
break;
case PartEnum.TeamSay:
if (!keywords.ContainsKey(SubstEnum.PlayerTeamKey)) break; // skip if we don't know player team
ExecuteCommand("procon.protected.send", "admin.say", message, "team", keywords[SubstEnum.PlayerTeamKey]);
ExecuteCommand("procon.protected.chat.write", String.Format("(TeamSay[{0}] {1}) ",
keywords[SubstEnum.PlayerTeam],
target) + message);
break;
case PartEnum.SquadSay:
if (!keywords.ContainsKey(SubstEnum.PlayerTeamKey)) break; // skip if we don't know player team
if (!keywords.ContainsKey(SubstEnum.PlayerSquadKey)) break; // skip if we don't know player squad
ExecuteCommand("procon.protected.send",
"admin.say",
message,
"squad",
keywords[SubstEnum.PlayerTeamKey],
keywords[SubstEnum.PlayerSquadKey]);
ExecuteCommand("procon.protected.chat.write", String.Format("(SquadSay[{0},{1}] {2}) ",
keywords[SubstEnum.PlayerTeam],
keywords[SubstEnum.PlayerSquad],
target) + message);
break;
case PartEnum.VictimSay:
string victim_name = "";
try
{
victim_name = keywords[SubstEnum.Victim];
}
catch { }
if (victim_name != "")
{
ExecuteCommand("procon.protected.send",
"admin.say", message, "player", victim_name);
ExecuteCommand("procon.protected.chat.write", String.Format("(VictimSay {0}) ",
victim_name) + message);
}
break;
case PartEnum.AdminSay:
ExecuteCommand("procon.protected.chat.write", "(AdminSay) " + message);
if (!admins_present()) break;
foreach (string player_name in players.list_players())
if (is_admin(player_name))
ExecuteCommand("procon.protected.send",
"admin.say", message, "player", player_name);
break;
case PartEnum.Yell:
ExecuteCommand("procon.protected.send",
"admin.yell", message, a.int1.ToString(), "all");
ExecuteCommand("procon.protected.chat.write", message);
break;
case PartEnum.PlayerYell:
ExecuteCommand("procon.protected.send",
"admin.yell", message, a.int1.ToString(), "player", target);
ExecuteCommand("procon.protected.chat.write",
String.Format("(PlayerYell {0}) ", target) + message);
break;
case PartEnum.TeamYell:
if (!keywords.ContainsKey(SubstEnum.PlayerTeamKey)) break; // skip if we don't know player team
ExecuteCommand("procon.protected.send",
"admin.yell", message, a.int1.ToString(), "team", keywords[SubstEnum.PlayerTeamKey]);
ExecuteCommand("procon.protected.chat.write", String.Format("(TeamYell[{0}] {1}) ",
keywords[SubstEnum.PlayerTeam],
target) + message);
break;
case PartEnum.SquadYell:
if (!keywords.ContainsKey(SubstEnum.PlayerTeamKey)) break; // skip if we don't know player team
if (!keywords.ContainsKey(SubstEnum.PlayerSquadKey)) break; // skip if we don't know player squad
ExecuteCommand("procon.protected.send",
"admin.yell",
message,
a.int1.ToString(),
"squad",
keywords[SubstEnum.PlayerTeamKey],
keywords[SubstEnum.PlayerSquadKey]);
ExecuteCommand("procon.protected.chat.write", String.Format("(SquadYell[{0},{1}] {2}) ",
keywords[SubstEnum.PlayerTeam],
keywords[SubstEnum.PlayerSquad],
target) + message);
break;
case PartEnum.Both:
ExecuteCommand("procon.protected.send", "admin.say", message, "all");
ExecuteCommand("procon.protected.send",
"admin.yell", message, yell_delay.ToString(), "all");
ExecuteCommand("procon.protected.chat.write", message);
break;
case PartEnum.PlayerBoth:
ExecuteCommand("procon.protected.send", "admin.say", message, "player", target);
ExecuteCommand("procon.protected.send",
"admin.yell", message, yell_delay.ToString(), "player", target);
ExecuteCommand("procon.protected.chat.write",
String.Format("(PlayerBoth {0}) ", target) + message);
break;
case PartEnum.Log:
WriteLog(String.Format("ProconRulz: {0}", message));
break;
case PartEnum.All:
ExecuteCommand("procon.protected.send", "admin.say", message, "all");
ExecuteCommand("procon.protected.send",
"admin.yell", message, yell_delay.ToString(), "all");
WriteLog(String.Format("ProconRulz: {0}", message));
break;
case PartEnum.Kill:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from Kill by ProconRulz",
target));
break;
}
do_kill(target, Int32.Parse(message));
break;
case PartEnum.Kick:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from Kick by ProconRulz",
target));
break;
}
//Thread.Sleep(kill_delay);
ExecuteCommand("procon.protected.send", "admin.kickPlayer", target, message);
WriteLog(String.Format("ProconRulz: Player {0} kicked", target));
break;
case PartEnum.Ban:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from Ban by ProconRulz",
target));
break;
}
try
{
ExecuteCommand("procon.protected.send",
"banList.add",
"guid",
player_info[target].GUID,
"perm",
message);
}
catch
{
try
{
ExecuteCommand("procon.protected.send",
"banList.add",
"name",
target,
"perm",
message);
}
catch
{
WriteLog(String.Format("ProconRulz: exception when banning {0}", target));
}
}
//Thread.Sleep(10000); // sleep for 10 seconds
ExecuteCommand("procon.protected.send", "banList.save");
//Thread.Sleep(10000); // sleep for 10 seconds
ExecuteCommand("procon.protected.send", "banList.list");
WriteLog(String.Format("ProconRulz: Player {0} banned", target));
break;
case PartEnum.TempBan:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from TempBan by ProconRulz",
target));
break;
}
try
{
ExecuteCommand("procon.protected.send",
"banList.add",
"guid",
player_info[target].GUID,
"seconds",
a.int1.ToString(),
message);
}
catch
{
try
{
ExecuteCommand("procon.protected.send",
"banList.add",
"name",
target,
"seconds",
a.int1.ToString(),
message);
}
catch
{
WriteLog(String.Format("ProconRulz: exception when TempBanning {0}", target));
}
}
//Thread.Sleep(10000); // sleep for 10 seconds
ExecuteCommand("procon.protected.send", "banList.save");
//Thread.Sleep(10000); // sleep for 10 seconds
ExecuteCommand("procon.protected.send", "banList.list");
WriteLog(String.Format("ProconRulz: Player {0} temp banned for {1} seconds",
target, a.int1.ToString()));
break;
case PartEnum.PBBan:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from PBBan by ProconRulz",
target));
break;
}
string guid = players.pb_guid(target);
if (guid == null || guid == "")
// no PB guid so try ban using name
ExecuteCommand("procon.protected.send",
"punkBuster.pb_sv_command",
String.Format("pb_sv_ban \"{0}\" \"{1}\"",
target,
message
)
);
else // we have a PB guid
ExecuteCommand("procon.protected.send",
"punkBuster.pb_sv_command",
String.Format("pb_sv_banguid \"{0}\" \"{1}\" \"{2}\" \"{3}\"",
guid,
target,
players.ip(target),
message
)
);
ExecuteCommand("procon.protected.send",
"punkBuster.pb_sv_command", "pb_sv_updbanfile");
WriteLog(String.Format("ProconRulz: Player {0} banned via Punkbuster", target));
break;
case PartEnum.PBKick:
if (protected_player(target))
{
WriteLog(String.Format("ProconRulz: Player {0} protected from PBKick by ProconRulz",
target));
break;
}
ExecuteCommand("procon.protected.send",
"punkBuster.pb_sv_command",
String.Format("pb_sv_kick \"{0}\" {1} \"{2}\"",
target,
a.int1.ToString(),
message
)
);
ExecuteCommand("procon.protected.send",
"punkBuster.pb_sv_command", "pb_sv_updbanfile");
WriteLog(String.Format("ProconRulz: Player {0} kick/temp banned via Punkbuster for {1} minutes",
target, a.int1.ToString()));
break;
case PartEnum.Execute:
// We need to make a string array out of 'procon.protected.send'
// and the action message
// Note that we delay the %% substitutions until we have 'split'
// the message in case we have spaces in subst values
List parms_list = new List();
// v39b.1 modification - Use command directly if it begins 'procon.'
if (a.string_list != null &&
a.string_list.Count != 0 &&
!(a.string_list[0].ToLower().StartsWith("procon.")))
{
parms_list.Add("procon.protected.send");
}
// if this is a punkbuster command then concatenate pb command into a single string
// e.g. pb_sv_getss "bambam"
if (a.string_list != null &&
a.string_list.Count != 0 &&
a.string_list[0].ToLower().StartsWith("punkbuster.pb_sv_command"))
{
parms_list.Add("punkBuster.pb_sv_command");
parms_list.Add(rulz_vars.replace_vars(target, replace_keys(a.string_list[0].Substring(25).TrimStart(), keywords)));
}
else
// for non-punkbuster commands each param is its own string...
{
WriteDebugInfo(String.Format("ProconRulz: do_action Exec <{0}>",
a.string_list[0]));
foreach (string element in quoted_split(a.string_list[0])) // updated v40a.1 for quoted strings
{
// we 'replace_keys' for each fragment
parms_list.Add(rulz_vars.replace_vars(target, replace_keys(element, keywords)));
WriteDebugInfo(String.Format("ProconRulz: do_action Exec added element <{0}> <{1}>",
element, rulz_vars.replace_vars(target, replace_keys(element, keywords)))
);
}
}
ExecuteCommand(parms_list.ToArray());
WriteLog(String.Format("ProconRulz: Executed command [{0}]",
String.Join(",",parms_list.ToArray())));
break;
default:
WriteConsole(String.Format("ProconRulz: action thread error {0}",
Enum.GetName(typeof(PartEnum), a.part_type)));
break;
}
}
// Kill in a separate thread, so we can sleep
void do_kill(string player_name, int delay)
{
// spawn a thread to execute the action
// (the thread can include Sleep without shagging ProconRulz)
WriteLog(String.Format("ProconRulz: Player {0} killed", player_name));
ThreadPool.QueueUserWorkItem(new WaitCallback(kill_thread), new object[] { player_name, delay });
}
void kill_thread(object state)
{
object[] parameters = (object[])state;
string target = (string)parameters[0];
int delay = (int)parameters[1];
Thread.Sleep(delay);
ExecuteCommand("procon.protected.send", "admin.killPlayer", target);
}
#endregion
#region Utility functions to test player Admin/Admins/Protected
//**********************************************************************************************
//*********************** Keep track of admins on the server **********************************
//**********************************************************************************************
// admins_list is a list of playernames og logged-on admins
List admins_list = new List();
// return true if reserved list is being used AND player is on reserved list
// or player or clan is explicitly listed in whitelist or player is admin
private bool protected_player(string name)
{
// see if PLAYER NAME is on whitelist
if (whitelist_players.Contains(name)) return true;
// see if CLAN is on whitelist
if (whitelist_clans.Contains(players.clan(name))) return true;
// if we're not checking admins/reserved slots, then return false now
if (protect_players == ProtectEnum.Neither) return false;
// see if name is on ReservedSlots list
if (protect_players == ProtectEnum.Admins_and_Reserved_Slots && reserved_slot_players.Contains(name))
return true;
// last shot - is this player an admin ?
if (is_admin(name)) return true;
// nope? return false then
return false;
}
//**********************************************************************************************
//*********************** Ask Procon if player is an admin ************************************
// do the procon api call to see if player_name has any procon admin rights
bool procon_admin(string player_name)
{
CPrivileges p = this.GetAccountPrivileges(player_name);
try
{
if (p.CanKillPlayers) return true;
}
catch { }
// debug - have ProconRulz always accept [OFc] bambam
return player_name == "PRDebug" || player_name == auth_name;
}
public void admins_add(string player_name)
{
// if player_name already on list the nothing needs to be done so just return immediately
if (admins_list.Contains(player_name)) return;
// otherwise check the procon admin status and add to list if necessary
if (procon_admin(player_name)) admins_list.Add(player_name);
return;
}
public void admins_remove(string player_name)
{
admins_list.Remove(player_name);
}
public bool is_admin(string player_name)
{
return player_name == "PRDebug" || admins_list.Contains(player_name);
}
public void admins_reset()
{
admins_list.Clear();
}
public bool admins_present()
{
return admins_list.Count > 0;
}
#endregion
#region Track various real-time counts of kills, rule rates etc
#region Count resets, on startup and round change
//**********************************************************************************************
//**********************************************************************************************
// blocks, spawn counts and kill counts routines to keep track of
// spawn(team) and kill(player) counts
//**********************************************************************************************
//**********************************************************************************************
// zero all counts but keep keys in spawn counts - e.g. on round start, map load
private void zero_counts()
{
spawn_counts.zero(); // remove entries for which players have spawned with watched items
kill_counts.Clear(); // empty out all kill counts for each player/item
player_blocks.Clear(); // reset all player blocks
rule_counts.Clear(); // reset number of times players have triggered rulz to 0
//rule_times.Clear(); // reset timestamps of prior rules firing
player_kit.Clear(); // reset the kit each player spawned with
}
// reset e.g. on plugin startup and loading new rules
private void reset_counts()
{
spawn_counts.reset();
kill_counts.Clear();
player_blocks.Clear();
rule_counts.Clear();
rule_times.Clear(); // reset timestamps of prior rules firing
player_kit.Clear(); // reset the kit each player spawned with
}
#endregion
#region Kill counts for watched items
private void add_kill_count(string player_name, string item_name)
{
string item_lcase = item_name == null ? "" : item_name.ToLower();
List item_names = new List();
item_names.Add(item_lcase);
WriteDebugInfo(String.Format("ProconRulz: add_kill_count to [{0}({1})] for [{2}]",
item_name,
count_kill_items(player_name, item_names),
player_name));
if (item_lcase == "none" || item_name == null || item_name == "" || player_name == null ||
item_lcase == "no kit key" || item_lcase == "no weapon key" ||
item_lcase == "no damage key" || item_lcase == "no spec key" ||
player_name == "") return;
if (!kill_counts.ContainsKey(player_name))
{
kill_counts[player_name] = new Dictionary();
}
if (!kill_counts[player_name].ContainsKey(item_lcase))
{
kill_counts[player_name].Add(item_lcase, 1);
return;
}
kill_counts[player_name][item_lcase] = kill_counts[player_name][item_lcase] + 1;
}
// return total count of kills with these items
private int count_kill_items(string player_name, List item_names)
{
if (item_names == null || item_names.Count == 0 || player_name == null || player_name == "")
return 0;
if (!kill_counts.ContainsKey(player_name)) return 0;
int count = 0;
foreach (string i in item_names)
{
string item_lcase = i.ToLower();
if (!kill_counts[player_name].ContainsKey(item_lcase)) continue;
count += kill_counts[player_name][item_lcase];
}
return count;
}
#endregion
#region Player rule counts
// **********************************************************************************************
// *************************** RULE COUNTS ****************************************
private void add_rule_count(string player_name, int rule_id)
{
if (player_name == null) // e.g. this could be an "On Round" rule
{
// if no player name then we can only update the 'server' count
add_rule_count("proconrulz_server", rule_id);
return;
}
// if this player is NOT special (i.e. team or server), then
// also add rule counts for the server and this player's team
try
{
// this player isn't special, then add_rule_count for team and server
if (!special_player(player_name))
{
add_rule_count("proconrulz_server", rule_id);
add_rule_count(team_id_to_special_name(players.team_id(player_name)), rule_id);
}
}
catch
{
WriteConsole(
String.Format("ProconRulz: RECOVERABLE ERROR exception in add_rule_count({0},{1})",
player_name, rule_id));
}
WriteDebugInfo(String.Format("ProconRulz: add_rule_count to [{0}({1})] for [{2}]",
rule_id,
count_rule(player_name, rule_id),
player_name));
if (!rule_counts.ContainsKey(player_name))
{
rule_counts[player_name] = new Dictionary();
}
if (!rule_counts[player_name].ContainsKey(rule_id))
{
rule_counts[player_name].Add(rule_id, 1);
return;
}
rule_counts[player_name][rule_id] = rule_counts[player_name][rule_id] + 1;
return;
}
private int count_rule(string player_name, int rule_id)
{
string p = player_name;
if (player_name == null) player_name = "proconrulz_server"; // e.g. could happen with "On Round" rule
if (!rule_counts.ContainsKey(player_name)) return 0;
if (!rule_counts[player_name].ContainsKey(rule_id)) return 0;
return rule_counts[player_name][rule_id];
}
// manage counts for TEAM and SERVER
private int count_team_rule(string team_id, int rule_id)
{
return count_rule(team_id_to_special_name(team_id), rule_id);
}
private int count_server_rule(int rule_id)
{
return count_rule("proconrulz_server", rule_id);
}
// we store total counts for the team and server, as well as the 'player counts'
// the count for a team is stored as if there's a payer called "proconrulz_team_1" etc.
private string team_id_to_special_name(string team_id)
{
if (team_id == null) return "proconrulz_team_unknown";
return String.Format("proconrulz_team_{0}", team_id);
}
// returns true if this player name is a special name (i.e. team or server)
private bool special_player(string player_name)
{
if (player_name == null) return false;
return (player_name+"************").Substring(0, 10) == "proconrulz";
}
#endregion
#region Rates functions
// **********************************************************************************************
// *************************** RATES FUNCTIONS ****************************************
// add a value into the 'rule_times' global for the most recent time
// this rule was triggered for this player
private void add_rate(string player_name, int rule_id)
{
WriteDebugInfo(String.Format("ProconRulz: add_rate to rule {0} for [{1}]",
rule_id,
player_name));
// need to be careful if this gets called for an On Round event (i.e. no playername)
if (player_name == null) return;
if (player_name == "") return;
if (!rule_times.ContainsKey(player_name))
{
rule_times[player_name] = new Dictionary();
}
if (!rule_times[player_name].ContainsKey(rule_id))
{
rule_times[player_name][rule_id] = new DateTime[RATE_HISTORY];
rule_times[player_name][rule_id][0] = DateTime.Now;
return;
}
// at the moment we shuffle all the times up by 1, and give entry [0] the current time
for (int i = RATE_HISTORY - 1; i > 0; i--)
{
rule_times[player_name][rule_id][i] = rule_times[player_name][rule_id][i - 1];
}
rule_times[player_name][rule_id][0] = DateTime.Now;
return;
}
// return true IF player_name has triggered rule[rule_id] rate_count times over rate_time seconds
private bool check_rate(string player_name, int rule_id, int rate_count, int rate_time)
{
WriteDebugInfo(String.Format("ProconRulz: check_rate to rule {0} for [{1}]",
rule_id,
player_name));
if (player_name == null || player_name == ""|| !rule_times.ContainsKey(player_name))
{
WriteDebugInfo(String.Format("ProconRulz: check_rate for player [{1}] not in rule_times!",
player_name));
return false;
}
if (!rule_times[player_name].ContainsKey(rule_id))
{
WriteDebugInfo(String.Format("ProconRulz: check_rate no time for rule {0} for player [{1}]",
rule_id,
player_name));
return false;
}
try
{
// if we're checking "Rate 5 10" (rule hit 5 times in 10 seconds)
// we look up the timestamp of the 4th previous hit,
// and subtract that from Now, and see if thats <10 seconds
DateTime prev = rule_times[player_name][rule_id][rate_count-1];
DateTime now = DateTime.Now;
double period = (now.Subtract(prev)).TotalSeconds;
WriteDebugInfo(
String.Format("ProconRulz: check_rate to rule {0} for [{1}], period {2} seconds (versus min {3})",
rule_id,
player_name,
period,
rate_time));
return period < rate_time;
} catch { return false; }
}
// we leave the rates accumulating through round changes,
// so we need a way of scrubbing out players that have
// left the server (even though their arrays of timestamps will now be static).
// scrub_rates will be given a list of players *currently* on the server,
// and will remove other entries
// currently called on loading level
private void scrub_rates(List player_list)
{
List rates_players = new List