View Single Post
  #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