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

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #1  
Old 08-29-2008, 04:40 PM
AndMetal
Developer
 
Join Date: Mar 2007
Location: Ohio
Posts: 648
Default Spell Blocking (now with Intra-Zone restrictions!)

Apparently in my original post, I just put %i instead of %.3f for the coordinates, so they weren't being executed correctly in the query. Here is the final code:

In zone/zonedb.h, after
Code:
   47 struct PetRecord {
   48 	uint32 npc_type;
   49 	bool temporary;
   50 };
add
Code:
// Spell Blocking by AndMetal
struct SpellBlocking_Struct{
	sint32	id;			// -1 = database error, 0 = no matches in the database, 1+ = id in the blocked_spells table
	int8	type;		// 0 = not blocked, 1 = zone wide, 2 = specific coords in the zone
	char*	message;	// Message to send to the client saying the message failed, for customizability
};
and a little farther down, after
Code:
  277 	/*
  278 	 * Misc stuff.
  279 	 * PLEASE DO NOT ADD TO THIS COLLECTION OF CRAP UNLESS YOUR METHOD
  280 	 * REALLY HAS NO BETTER SECTION
  281 	 */
  282 	bool    logevents(const char* accountname,int32 accountid,int8 status,const char* charname,const char* target, const char* descriptiontype, const char* description,int event_nid);
  283 	void	GetEventLogs(const char* name,char* target,int32 account_id=0,int8 eventid=0,char* detail=0,char* timestamp=0, CharacterEventLog_Struct* cel=0);
add
Code:
	// Spell Blocking by AndMetal
	SpellBlocking_Struct	GetSpellBlock(int16 spell_id, uint16 zone_id, float x, float y, float z);
In zone/zonedb.cpp, after
Code:
 1268 /*
 1269  solar: this is never actually called, client_process starts an async query
 1270  instead and uses GetAccountInfoForLogin_result to process it..
 1271  */
 1272 bool ZoneDatabase::GetAccountInfoForLogin(int32 account_id, sint16* admin, char* account_name, int32* lsaccountid, int8* gmspeed, bool* revoked,bool* gmhideme) {
 1273 	char errbuf[MYSQL_ERRMSG_SIZE];
 1274     char *query = 0;
 1275     MYSQL_RES *result;
 1276 
 1277 	if (RunQuery(query, MakeAnyLenString(&query, "SELECT status, name, lsaccount_id, gmspeed, revoked, hideme FROM account WHERE id=%i", account_id), errbuf, &result)) {
 1278 		safe_delete_array(query);
 1279 		bool ret = GetAccountInfoForLogin_result(result, admin, account_name, lsaccountid, gmspeed, revoked,gmhideme);
 1280 		mysql_free_result(result);
 1281 		return ret;
 1282 	}
 1283 	else
 1284 	{
 1285 		cerr << "Error in GetAccountInfoForLogin query '" << query << "' " << errbuf << endl;
 1286 		safe_delete_array(query);
 1287 		return false;
 1288 	}
 1289 
 1290 	return false;
 1291 }
add
Code:
/* Spell Blocking by AndMetal
To Do: load blocked_spells table into shared memory, should keep the database from exploding, but as-is allows for changes to take effect immediately
*/
SpellBlocking_Struct ZoneDatabase::GetSpellBlock(int16 spell_id, uint16 zone_id, float x, float y, float z) {
	// Database stuff
	char errbuf[MYSQL_ERRMSG_SIZE];
    char *query = 0;
    MYSQL_RES *result;
    MYSQL_ROW row;

	SpellBlocking_Struct blocked;
	blocked.id = 0;

	if(RunQuery(query, MakeAnyLenString(&query, "SELECT bs.id, bs.type, bs.message FROM blocked_spells bs "
		"WHERE bs.zoneid = %i "
		"AND bs.spellid = %i "
		// Create a virtual box of spell blockage, but still include NULLs
		"AND ((%.3f BETWEEN (bs.x - bs.x_diff) AND (bs.x + bs.x_diff)) OR (x IS NULL) OR (x_diff IS NULL)) " 
		"AND ((%.3f BETWEEN (bs.y - bs.y_diff) AND (bs.y + bs.y_diff)) OR (y IS NULL) OR (y_diff IS NULL))"
		"AND ((%.3f BETWEEN (bs.z - bs.z_diff) AND (bs.z + bs.z_diff)) OR (z IS NULL) OR (z_diff IS NULL))"
		// Type 2s first, in case there is a message to show for that specific spot in the zone
		"ORDER BY bs.type DESC, bs.id ASC LIMIT 1", zone_id, spell_id, x, y, z), errbuf, &result)) {
		safe_delete_array(query);
		if(mysql_num_rows(result) != 0) { // Did we get anything from the query?
			row = mysql_fetch_row(result);
			blocked.id = atoi(row[0]);
			blocked.type = atoi(row[1]);
			blocked.message = row[2];
			mysql_free_result(result);
		}
		else
			mysql_free_result(result);
	}
	else {
		blocked.id = -1;
		cerr << "Error in GetSpellBlock query '" << query << "' " << errbuf << endl;
		safe_delete_array(query);
	}
	return blocked;
}
Lastly, in zone/spells.cpp, after
Code:
 1254 	// angelox start
 1255 
 1256 	if(IsEffectInSpell(spell_id, SE_Levitate) && !zone->CanLevitate()){
 1257 			if(IsClient()){
 1258 				if(!CastToClient()->GetGM()){
 1259 					Message(13, "You can't levitate in this zone.");
 1260 					return false;
 1261 				}
 1262 			}
 1263 		}
add
Code:
	// Spell Blocking by AndMetal
	if(IsClient() && !CastToClient()->GetGM()) { // First the easy stuff, should pass over this if they're a regular mob
		SpellBlocking_Struct blocked = database.GetSpellBlock(spell_id, GetZoneID(), GetX(), GetY(), GetZ());
		if(blocked.id > 0) {
			if(blocked.message != NULL) 
				Message(13, blocked.message); // Output message from the DB
			else if(blocked.type == 2)
				Message(13, "You can't cast this spell here. Try somewhere else."); // Default client message, hinting that it's just that part of the zone
			else if(blocked.type == 1)
				Message(13, "You can't cast this spell here."); //Default client message
			return false; // Prevents the spell from casting
		}
	}
New table, blocked_spells
Code:
CREATE TABLE `blocked_spells` (
  `id` int(11) NOT NULL auto_increment,
  `spellid` mediumint(8) unsigned NOT NULL default '0',
  `type` tinyint(4) NOT NULL default '0',
  `zoneid` int(4) NOT NULL default '0',
  `x` float default NULL,
  `y` float default NULL,
  `z` float default NULL,
  `x_diff` float default NULL,
  `y_diff` float default NULL,
  `z_diff` float default NULL,
  `message` varchar(255) default NULL,
  `description` varchar(255) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM;
and a few starting values
Code:
INSERT INTO blocked_spells (spellid, type, zoneid, message, description) VALUES (1771, 1, 71, 'A voice whispers in your mind: "There are no heroes here..."', 'Prevent CoH in airplane');
INSERT INTO blocked_spells (spellid, type, zoneid, x, y, z, x_diff, y_diff, z_diff, message, description) VALUES (1771, 2, 162, 657, -325, 418.6, 44.6, 44.6, 15, 'A voice whispers in your mind: "There are no heroes here..." Try somewhere else.', 'Prevent CoH in ssratemple before Emperor\'s Room');
This time I did test out the intra-zone portion (in this case, in ssratemple), and it does prevent the casting & gives the correct messages.

Long term, it would probably be better to load the blocked_spells table into shared memory, otherwise a query is run against the database every single time a client casts a spell and doesn't have a GM flag. In addition, it might be worth exploring adding this check both at the end of the spell being cast, as well as at the beginning. Otherwise, I hope others fine this method more useful than the hard-coded blocks currently in the source.
__________________
GM-Impossible of 'A work in progress'
A non-legit PEQ DB server
How to create your own non-legit server

My Contributions to the Wiki
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 12:27 PM.


 

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 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3