Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #18  
Old 06-04-2008, 01:35 AM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Here is the best diff I can get for changing from the old code to the new code. I think I have everything here, but there could definitely be a mistake or 2. I think it should at least be a place to start. I had to add in vague line numbers because I don't have the exact lines where they should be in the 1110 code.

Code:
Index: zone/client.h
@@ Somewhere near Line 155 @@
 	WarpDetected,			// Lieka:  Return client to pre-warp location

@@ Somewhere near Line 190 @@
<	bool	WarpDetection(bool CTimer, float Distance);
	bool	WarpDetection(bool CTimer, float Distance, int32 last_ppu_timer);

Index: zone/client_packet.cpp
@@ Somewhere near Line 771 @@
< bool Client::WarpDetection(bool CTimer, float distance)
< {	
< 	float last_distance;
< 	if (threshold_timer.GetRemainingTime() < 1 && ((RuleR(Zone, MQWarpThresholdTimer)) != -1)) {   //Null:  If the timer is done, reset threshold, then reset timer //Lieka:  Integrated into Rules System.
< 		warp_threshold = (RuleR(Zone, MQWarpLagThreshold));  //Lieka:  Integrated warp_threshold value into Rules System.  Original Value was 140.
< 		threshold_timer.Start((RuleR(Zone, MQWarpThresholdTimer)), false); //Lieka:  Integrated timer duration value into the Rules System.  Original Value was 90000 (90 seconds).
< 	}
< 	if ((CTimer))
< 		return false;
< 	else
< 	{
< 		//Null Edit:  made warp detector fire only when the sum of all the warps in a period of time are greater than a threshold
< 		//this makes the warp detector more lax on small warps, but still drops the hammer on the big ones.
< 		if (distance>140.0f) {
< 			last_distance = (distance-140.0f);
< 			warp_threshold -= last_distance;
< 			last_warp_distance = last_distance;
< 		}
< 	   return (warp_threshold < 0); //Null:  If the threshold is met, hit them with the hammer
< 	}
< }
< void Client::CheatDetected(CheatTypes CheatType)
< { //[Paddy] ToDo: Break warp down for special zones. Some zones have special teleportation pads or bad .map files which can trigger the detector without a legit zone request.
< 	switch (CheatType)
< 	{
< 		case MQWarp://Some zones have serious issues, turning off warp flags for these zones.
< 			if(!((zone->GetZoneID()==2)/*qeynos2*/ || (zone->GetZoneID()==9)/*freportw*/|| (zone->GetZoneID()==10)/*freporte*/ || (zone->GetZoneID()==34)/*nro*/ || (zone->GetZoneID()==24)/*erudin*/ || (zone->GetZoneID()==75)/*Paineel*/ || (zone->GetZoneID()==62)/*Felwitheb*/) && (RuleB(Zone, EnableMQWarpDetector) && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) || (RuleI(Zone, MQWarpExemptStatus)) == -1)))) //Lieka:  Exempt these zones from the MQWarp detector (This may be depricated now, but these zones were problems in the past)
< 			{
< 				Message(13, "Your account has been reported for hacking.");
< 				database.SetMQDetectionFlag(this->account_name,this->name, "/MQWarp", zone->GetShortName());
< 				SetMana(0);  //Lieka:  Remove all mana from player.
< 				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
< 				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
< 				SpellFinished((RuleI(Zone, MQWarpDetectionSpellID)), this);  //Lieka:  Integrated into Rules System.  Spell to cast on players Default:  757 (Resurrection Effects).
< 				worldserver.SendEmoteMessage(0,0,0,13,"<MQWarp Detector>.  %s was just caught warping in %s.  Come get your free kill!",this->GetName(),zone->GetLongName());
< 				warp_threshold = 1;   //Null:  bringing the detector back up to one to avoid chain detections.
< 			}
< 			break;
< 		case MQZone:
< 			if(!( (zone->GetZoneID()==31)/*sola*/ || (zone->GetZoneID()==32)/*solb*/ || (zone->GetZoneID()==25)/*nek*/ || (zone->GetZoneID()==27)/*lava*/ ) && (RuleB(Zone, EnableMQZoneDetector))&& ((this->Admin() < RuleI(Zone, MQZoneExemptStatus) || (RuleI(Zone, MQZoneExemptStatus)) == -1))) //Lieka:  Exempt these zones from the MQZone detector (This may be depricated now, but were problems in the past)
< 			{
< 				Message(13, "Your account has been reported for hacking.");
< 				database.SetMQDetectionFlag(this->account_name,this->name, "/MQZone", zone->GetShortName());
< 				SetMana(0);  //Lieka:  Remove all mana from player.
< 				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
< 				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
< 				AddBuff(this,(RuleI(Zone, MQZoneDetectionSpellID)),30);   //Lieka:  Integrated into Rules System.  Add (de)buff on player for 30 ticks.  Default:  757 (Resurrection Effects).
< 				worldserver.SendEmoteMessage(0,0,0,13,"<MQZone Detector>.  %s as just caught using Macroquest to /Zone to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQZone detector has caught a cheater.
< 			}
< 			break;
< 		case MQGate:
< 			if (RuleB(Zone, EnableMQGateDetector)&& ((this->Admin() < RuleI(Zone, MQGateExemptStatus) || (RuleI(Zone, MQGateExemptStatus)) == -1))) {
< 				Message(13, "Your account has been reported for hacking.");
< 				database.SetMQDetectionFlag(this->account_name,this->name, "/MQGate", zone->GetShortName());
< 				this->SetZone(this->GetZoneID()); //Lieka:  Prevent the player from zoning, place him back in the zone where he tried to originally /gate.
< 				SetMana(0);  //Lieka:  Remove all mana from player.
< 				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
< 				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
< 				AddBuff(this,(RuleI(Zone, MQGateDetectionSpellID)),30);   //Lieka:  Integrated into Rules System.  Add (de)buff on player for 30 ticks.  Default:  757 (Resurrection Effects).
< 				worldserver.SendEmoteMessage(0,0,0,13,"<MQGate Detector>.  %s was just caught using Macroquest to /Gate to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQGate Detector has caught a cheater.
< 			}
< 			break;
< 		case MQGhost: //Lieka:  Not currently implemented, but the framework is in place - just needs detection scenarios identified
< 			if (RuleB(Zone, EnableMQGhostDetector) && ((this->Admin() < RuleI(Zone, MQGhostExemptStatus) || (RuleI(Zone, MQGhostExemptStatus)) == -1))) {
< 				Message(13, "Your account has been reported for hacking.");
< 				database.SetMQDetectionFlag(this->account_name,this->name, "/MQGhost", zone->GetShortName());
< 				SetMana(0);  //Lieka:  Remove all mana from player.
< 				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
< 				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
< 				SpellFinished((RuleI(Zone, MQGhostDetectionSpellID)), this);  //Lieka:  Integrated into Rules System.  Spell to cast on players Default:  757 (Resurrection Effects).
< 				worldserver.SendEmoteMessage(0,0,0,13,"<MQGhost Detector>.  %s was just caught using Macroquest to /Ghost to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQGate Detector has caught a cheater.	
< 			}
< 			break;
< 	}
< }

bool Client::WarpDetection(bool CTimer, float distance, int32 last_ppu_timer) //Lieka:  Completely reworked this function.  Rather than basing warp detection on large movement diffs, we now base it on the player's speed (update distance / update time)
{	
	if (CTimer)
		return false;
	else
	{
	movement_speed = distance / (last_ppu_timer/100);
	if (movement_speed > highest_movement_speed) {
		highest_movement_speed = movement_speed;
	}
	return (movement_speed > RuleR(Zone, MQWarpSpeedLimit)); //Lieka:  If the player breaks the speed limit, bring down the hammer.
	}
}

void Client::CheatDetected(CheatTypes CheatType)
{ //[Paddy] ToDo: Break warp down for special zones. Some zones have special teleportation pads or bad .map files which can trigger the detector without a legit zone request.
	switch (CheatType)
	{
		case MQWarp://Some zones have serious issues, turning off warp flags for these zones.
			if(!((zone->GetZoneID()==2)/*qeynos2*/ || (zone->GetZoneID()==9)/*freportw*/|| (zone->GetZoneID()==10)/*freporte*/ || (zone->GetZoneID()==34)/*nro*/ || (zone->GetZoneID()==24)/*erudin*/ || (zone->GetZoneID()==75)/*Paineel*/ || (zone->GetZoneID()==62)/*Felwitheb*/) && (RuleB(Zone, EnableMQWarpDetector) && ((this->Admin() < RuleI(Zone, MQWarpExemptStatus) || (RuleI(Zone, MQWarpExemptStatus)) == -1)))) //Lieka:  Exempt these zones from the MQWarp detector (This may be depricated now, but these zones were problems in the past)
			{
				if (!RuleB(Zone, MQDetectorDisableSQLLogging)){
				Message(13, "Your account has been reported for hacking.");
				database.SetMQDetectionFlag(this->account_name, this->name, "/MQWarp", zone->GetShortName());
				}
				if(!RuleB(Zone, MQDetectorDisablePenalties)){ //Lieka:  Toggle Negative effects based on rule value
				SetMana(0);  //Lieka:  Remove all mana from player.
				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
				SpellFinished((RuleI(Zone, MQWarpDetectionSpellID)), this);  //Lieka:  Integrated into Rules System.  Spell to cast on players Default:  757 (Resurrection Effects).
				}
				if(!RuleB(Zone, MQDetectorDisableBroadcast)) //Lieka:  Toggle broadcast based on rule value
					worldserver.SendEmoteMessage(0,0,0,13,"<MQWarp Detector>.  %s was just caught warping in %s.  Come get your free kill!",this->GetName(),zone->GetLongName());
			}
			break;
		case MQZone:
			if(!( (zone->GetZoneID()==31)/*sola*/ || (zone->GetZoneID()==32)/*solb*/ || (zone->GetZoneID()==25)/*nek*/ || (zone->GetZoneID()==27)/*lava*/ ) && (RuleB(Zone, EnableMQZoneDetector))&& ((this->Admin() < RuleI(Zone, MQZoneExemptStatus) || (RuleI(Zone, MQZoneExemptStatus)) == -1))) //Lieka:  Exempt these zones from the MQZone detector (This may be depricated now, but were problems in the past)
			{
				if (!RuleB(Zone, MQDetectorDisableSQLLogging)){
				Message(13, "Your account has been reported for hacking.");
				database.SetMQDetectionFlag(this->account_name, this->name, "/MQZone", zone->GetShortName());
				}
				if(!RuleB(Zone, MQDetectorDisablePenalties)){ //Lieka:  Toggle Negative effects based on rule value
				SetMana(0);  //Lieka:  Remove all mana from player.
				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
				AddBuff(this,(RuleI(Zone, MQZoneDetectionSpellID)),30);   //Lieka:  Integrated into Rules System.  Add (de)buff on player for 30 ticks.  Default:  757 (Resurrection Effects).
				}
				if(!RuleB(Zone, MQDetectorDisableBroadcast)) //Lieka:  Toggle broadcast based on rule value
					worldserver.SendEmoteMessage(0,0,0,13,"<MQZone Detector>.  %s as just caught using Macroquest to /Zone to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQZone detector has caught a cheater.
			}
			break;
		case MQGate:
			if (RuleB(Zone, EnableMQGateDetector)&& ((this->Admin() < RuleI(Zone, MQGateExemptStatus) || (RuleI(Zone, MQGateExemptStatus)) == -1))) {
				if (!RuleB(Zone, MQDetectorDisableSQLLogging)){
				Message(13, "Your account has been reported for hacking.");
				database.SetMQDetectionFlag(this->account_name, this->name, "/MQGate", zone->GetShortName());
				}
				this->SetZone(this->GetZoneID()); //Lieka:  Prevent the player from zoning, place him back in the zone where he tried to originally /gate.
				if(!RuleB(Zone, MQDetectorDisablePenalties)){ //Lieka:  Toggle Negative effects based on rule value
				SetMana(0);  //Lieka:  Remove all mana from player.
				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
				AddBuff(this,(RuleI(Zone, MQGateDetectionSpellID)),30);   //Lieka:  Integrated into Rules System.  Add (de)buff on player for 30 ticks.  Default:  757 (Resurrection Effects).
				}
				if(!RuleB(Zone, MQDetectorDisableBroadcast)) //Lieka:  Toggle broadcast based on rule value
					worldserver.SendEmoteMessage(0,0,0,13,"<MQGate Detector>.  %s was just caught using Macroquest to /Gate to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQGate Detector has caught a cheater.
			}
			break;
		case MQGhost: //Lieka:  Not currently implemented, but the framework is in place - just needs detection scenarios identified
			if (RuleB(Zone, EnableMQGhostDetector) && ((this->Admin() < RuleI(Zone, MQGhostExemptStatus) || (RuleI(Zone, MQGhostExemptStatus)) == -1))) {
				if (!RuleB(Zone, MQDetectorDisableSQLLogging)){
				Message(13, "Your account has been reported for hacking.");
				database.SetMQDetectionFlag(this->account_name, this->name, "/MQGhost", zone->GetShortName());
				}
				if(!RuleB(Zone, MQDetectorDisablePenalties)){ //Lieka:  Toggle Negative effects based on rule value
				SetMana(0);  //Lieka:  Remove all mana from player.
				SetHP(5);  //Lieka:  Set player's hitpoints to 5.
				BuffFadeAll();  //Lieka:  Wipe all of player's buffs.
				SpellFinished((RuleI(Zone, MQGhostDetectionSpellID)), this);  //Lieka:  Integrated into Rules System.  Spell to cast on players Default:  757 (Resurrection Effects).
				}
				if(!RuleB(Zone, MQDetectorDisableBroadcast)) //Lieka:  Toggle broadcast based on rule value
					worldserver.SendEmoteMessage(0,0,0,13,"<MQGhost Detector>.  %s was just caught using Macroquest to /Ghost to %s.  Come get your free kill!",this->GetName(),zone->GetLongName()); //Lieka:  Broadcast to the server that the MQGate Detector has caught a cheater.			
			}
			break;
	}
}

@@ Somewhere near Line 890 @@

	//Lieka:  Track duration between Player Position Updates to determine lag (for warp detection).
	if (ppu_timer.Enabled()) { 
		last_ppu_timer = 40000 - ppu_timer.GetRemainingTime(); 
		if(last_ppu_timer > longest_ppu_timer) {
			longest_ppu_timer = last_ppu_timer;
		}
	} else {
		last_ppu_timer = 1;
	}
	ppu_timer.Start(40000, true);

@@ Somewhere near Line 910 @@
< 	if ((this->cheat_timer.GetRemainingTime())>1 && (this->cheat_timer.Enabled())) //Lieka:  Check to see if the cheat (exemption) timer is active - this is for debugging
< 	{
< 		//Spell timer is currently active
< 		//worldserver.SendEmoteMessage(0,0,0,13,"Timer is Active.  %d True: %s",this->cheat_timer.GetRemainingTime(), (this->cheat_timer.GetRemainingTime()>1)? "true" : "false"); //Leika Edit:  Enable this to get debug messages.
< 	}
< 	else //Timer has elapsed or hasn't started, let's do a Warp Check
< 	{
< 		if ((WarpDetection(false, dist)) && ((admin <= RuleI(Zone, MQWarpExemptStatus)) || (RuleI(Zone, MQWarpExemptStatus) == -1))) //Exempt from warp detection if admin level is >  Rule:Zone:MQWarpExemptStatus
< 		{
< 			printf("Warping Detected by %S Acct: %s Distance: %f.", GetName(), AccountName(), GetLWDistance());
< 			CheatDetected(MQWarp); //Lieka:  Execute MQWarp function on offending player
< 		}

	if (((this->cheat_timer.GetRemainingTime())>1 && (this->cheat_timer.Enabled())) || longest_ppu_timer < 2) //Lieka:  Check to see if the cheat (exemption) timer is active - this is for debugging
	{
		//Spell timer is currently active
		//worldserver.SendEmoteMessage(0,0,0,13,"Timer is Active.  %d True: %s",this->cheat_timer.GetRemainingTime(), (this->cheat_timer.GetRemainingTime()>1)? "true" : "false"); //Leika Edit:  Enable this to get debug messages.
	}
	else //Timer has elapsed or hasn't started, let's do a Warp Check
	{
		if ((WarpDetection(false, dist, last_ppu_timer)) && ((RuleB(Zone,EnableMQGateDetector) && (admin <= RuleI(Zone, MQWarpExemptStatus)) || (RuleI(Zone, MQWarpExemptStatus) == -1)))) //Exempt from warp detection if admin level is >  Rule:Zone:MQWarpExemptStatus
		{
			printf("Warping Detected by %s Distance: %f.", GetName(), dist);
			this->CastToClient()->MovePC(zone->GetZoneID(), x_pos, y_pos, z_pos, heading, 2, WarpDetected); //Lieka Edit:  Move player back to pre-warp location
			CheatDetected(MQWarp); //Lieka:  Execute MQWarp function on offending player
			return;
			}

Index: zone/mob.cpp
@@ Somewhere near Line 100 @@
<		threshold_timer(0), //Lieka:  Timer to allow exemptions MQWarp related to lag

@@ Somewhere near Line 105 @@
		ppu_timer(0), //Lieka:  Timer to track time between Player Update Packets (to detect lag) for MQWarp detection

@@ Somewhere near Line 125 @@
<	warp_threshold = 140;            //Null:  set the threshold on creation of mob instance
<	last_warp_distance = 0;			 //Null: set this one to zero also just because.

	longest_ppu_timer = 0;			 //Lieka:  reset longest player packet update timer record on creation of mob instance
	last_ppu_timer = 0;				 //Lieka:  reset last player packet update timer record on creation of mob instance
	movement_speed = 0;				 //Lieka:  reset player movement speed record on creation of mob instance
	highest_movement_speed = 0;		 //Lieka:  reset highest recorded player movement speed on creation of mob instance

@@ Somewhere near Line 875 @@
<	client->Message(0, "  Last Warp Distance: %f Threshold Remaining: %f", GetLWDistance(), GetWarpThreshold());   //Null:  added this to check players last warp distance for debuging.

	client->Message(0, "  Last Player Position Update Timer: %i  Longest Player Position Update Timer: %i", GetLastPPUTimer(), GetLongestPPUTimer()); //Lieka:  added this to monitor player lag for MQWarp detection.
	client->Message(0, "  Current Movement Speed: %i  Highest Movement Speed: %i", GetMovementSpeed(), GetHighestMovementSpeed()); //Lieka:  added this to monitor player's movement speed (based on last PPU update / PPU time.








Index: zone/mob.h
@@ Somewhere near Line 455 @@
<	float GetLWDistance()					{ return last_warp_distance; }    //Null:  these are used to return the values to #showstats
<	float GetWarpThreshold()				{ return warp_threshold; }		  //this one too	

	int32 GetLastPPUTimer()					{ return last_ppu_timer; }  //Lieka:  Used in #showstats mainly for debugging and monitoring players
	int32 GetLongestPPUTimer()				{ return longest_ppu_timer; }  //Lieka:  Used in #showstats mainly for debugging and monitoring players
	int32 GetMovementSpeed()				{ return movement_speed; }	//Lieka:  Used in #showstats mainly for debugging and monitoring players
	int32 GetHighestMovementSpeed()			{ return highest_movement_speed; } //Lieka: Used in #showstats mainly for debugging and monitorint players	


@@ Somewhere near Line 785 @@
<	Timer threshold_timer;  //Null:  threshold timer
<	float warp_threshold;   //Null:  threshold for warp detector
<	float last_warp_distance;  //Null:  last distance logged as a warp, used for logs and #showstats

	Timer ppu_timer; //Lieka:  Timer used to track amount of time between Player Position Updates.
	int32 last_ppu_timer;  //Lieka:  Indicates last amount of time between the player's PPUs.
	int32 longest_ppu_timer; //Lieka:  indicates longest time that the player gone without a PPU (while in the current zone).
	float movement_speed;  //Lieka:  Calculated movement speed in units / second.
	float highest_movement_speed; //Lieka:  Highest movement speed reached.

Index: zone/spell_effects.cpp
@@ Somewhere near Line 1680 @@
					Message(15, "You have been summoned!");

Index: zone/zoning.cpp
@@ Somewhere near Line 430 @@
		case WarpDetected:
			Message(15, "Returning to pre-warp location.");
			ZonePC(zoneID, x, y, z, heading, ignorerestrictions, zm);
			break;

@@ Somewhere near Line 450 @@
		case WarpDetected:
			this->cheat_timer.Disable();
			zonesummon_x = x_pos = x;
			zonesummon_y = y_pos = y;
			zonesummon_z = z_pos = z;
			heading = heading;
			break;


Index: common/ruletypes.h
@@ Somewhere near Line 75 @@
<  RULE_REAL ( Zone, MQWarpDetectorDistance, 30 ) //Lieka:  Distance a player must travel between client to server location updates before a warp is registered.  30 allows for beyond GM speed without lag.
<  RULE_REAL ( Zone, MQWarpLagThreshold, 140 ) //Lieka:  Distance beyond the Zone:MQWarpDetectorDistance that a player must travel within the MQWarpThresholdTimer amount of time before tripping the MQWarp detector.  Set to 0 to disable this feature.
<  RULE_REAL ( Zone, MQWarpThresholdTimer, 90000 ) //Lieka:  Amount of time before the warp_threshold resets to the Zone:MQWarpLagThreshold value.  Default: 90000 (900 seconds/15 minutes).  Set to -1 to disable this feature.

RULE_REAL ( Zone, MQWarpSpeedLimit, 30 ) //Lieka:  Units / second that a player is allowed to travel before a warp is registered. At level 50 SoW = 4, GM Speed = 7 Default value: 10
RULE_BOOL ( Zone, MQDetectorDisablePenalties, false ) //Lieka:  Disable penalties (including spell IDs in other MQ rules above when a player triggers a detector.  Default value: false
RULE_BOOL ( Zone, MQDetectorDisableBroadcast, false ) //Lieka:  Disable the broadcast message when the use of MQ is detected.  Default Value:  false
RULE_BOOL ( Zone, MQDetectorDisableSQLLogging, false ) //Lieka:  Disable logging of MQDetector events into SQL.  Default Value:  false



Index: zone/client.cpp
@@ Somewhere near Line 2300 @@
<	return result>(RuleR(Zone, MQWarpDetectorDistance)); //Lieka:  Integrated into Rules System; default value is 30, this allows for beyond GM speed without lag.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
 


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 04:54 AM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3