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.