mirror of
https://github.com/EQEmu/Server.git
synced 2026-06-11 15:58:36 +00:00
Traps overhaul. New functionality has been added, while preserving the old functionality. Numerous bug fixes occurred as well.
Added column triggered_number. If this is set, then the trap will despawn after it has been triggered this number of times. If 0, the trap will never despawn on its own. Added group column. This allows developers to group traps together in a similar way as spawngroups for NPCs. When a trap that is grouped is despawned in anyway, a random trap in the group will take its place. Grouped traps do not have to be at the same coords or have the same type. This can allow for some spawning diversity if so required. If set to 0, the trap is not grouped and will always respawn. Added column despawn_when_triggered. If set to 1, then a trap will despawn when a player triggers it. If 0, then there will be a 5 second reset time and then the same trap will again be active. (Assuming triggered_number has not been reached.) The player that triggered the trap will not re-trigger it until they have left and re-enetered the trap's radius. Traps will no longer trigger on players that are currently zoning. This fixes some weirdness and at least one crash. The trap can trigger however after the connection is been completed. If a player camped out in a trap radius they can potentially still be hit. Alarm type traps were not using effectvalue2 to determine who should be aggroed. This is now fixed. Traps will no longer be broken by #repop, #depopzone, or #reloadworld. All 3 commands will now have the same effect on traps as they do for NPCs. Added command #reloadtraps. This reloads all of the traps in the zone. Added command #trapinfo. This gives some information about the traps currently spawned in the zone. Added Traps logsys category Required SQL: utils/sql/git/required/2017_10_26_traps.sql
This commit is contained in:
+248
-23
@@ -52,10 +52,12 @@ CREATE TABLE traps (
|
||||
Trap::Trap() :
|
||||
Entity(),
|
||||
respawn_timer(600000),
|
||||
chkarea_timer(500),
|
||||
chkarea_timer(1000),
|
||||
reset_timer(5000),
|
||||
m_Position(glm::vec3())
|
||||
{
|
||||
trap_id = 0;
|
||||
db_id = 0;
|
||||
maxzdiff = 0;
|
||||
radius = 0;
|
||||
effect = 0;
|
||||
@@ -64,12 +66,19 @@ Trap::Trap() :
|
||||
skill = 0;
|
||||
level = 0;
|
||||
respawn_timer.Disable();
|
||||
reset_timer.Disable();
|
||||
detected = false;
|
||||
disarmed = false;
|
||||
respawn_time = 0;
|
||||
respawn_var = 0;
|
||||
hiddenTrigger = nullptr;
|
||||
ownHiddenTrigger = false;
|
||||
chance = 0;
|
||||
triggered_number = 0;
|
||||
times_triggered = 0;
|
||||
group = 0;
|
||||
despawn_when_triggered = false;
|
||||
charid = 0;
|
||||
}
|
||||
|
||||
Trap::~Trap()
|
||||
@@ -80,8 +89,7 @@ Trap::~Trap()
|
||||
|
||||
bool Trap::Process()
|
||||
{
|
||||
if (chkarea_timer.Enabled() && chkarea_timer.Check()
|
||||
/*&& zone->GetClientCount() > 0*/ )
|
||||
if (chkarea_timer.Enabled() && chkarea_timer.Check() && !reset_timer.Enabled())
|
||||
{
|
||||
Mob* trigger = entity_list.GetTrapTrigger(this);
|
||||
if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM()))
|
||||
@@ -89,6 +97,13 @@ bool Trap::Process()
|
||||
Trigger(trigger);
|
||||
}
|
||||
}
|
||||
else if (reset_timer.Enabled() && reset_timer.Check())
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "Reset timer disabled in Reset Check Process for trap %d.", trap_id);
|
||||
reset_timer.Disable();
|
||||
charid = 0;
|
||||
}
|
||||
|
||||
if (respawn_timer.Enabled() && respawn_timer.Check())
|
||||
{
|
||||
detected = false;
|
||||
@@ -96,11 +111,15 @@ bool Trap::Process()
|
||||
chkarea_timer.Enable();
|
||||
respawn_timer.Disable();
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Trap::Trigger(Mob* trigger)
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "Trap %d triggered by %s for the %d time!", trap_id, trigger->GetName(), times_triggered + 1);
|
||||
|
||||
int i = 0;
|
||||
const NPCType* tmp = 0;
|
||||
switch (effect)
|
||||
@@ -128,7 +147,7 @@ void Trap::Trigger(Mob* trigger)
|
||||
entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str());
|
||||
}
|
||||
|
||||
entity_list.SendAlarm(this,trigger,effectvalue);
|
||||
entity_list.SendAlarm(this,trigger, effectvalue2);
|
||||
break;
|
||||
case trapTypeMysticSpawn:
|
||||
if (message.empty())
|
||||
@@ -201,12 +220,41 @@ void Trap::Trigger(Mob* trigger)
|
||||
safe_delete(outapp);
|
||||
}
|
||||
}
|
||||
respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000);
|
||||
chkarea_timer.Disable();
|
||||
disarmed = true;
|
||||
|
||||
if (trigger && trigger->IsClient())
|
||||
{
|
||||
trigger->CastToClient()->trapid = trap_id;
|
||||
charid = trigger->CastToClient()->CharacterID();
|
||||
}
|
||||
|
||||
bool update = false;
|
||||
if (despawn_when_triggered)
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "Trap %d is despawning after being triggered.", trap_id);
|
||||
update = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset_timer.Start(5000);
|
||||
}
|
||||
|
||||
if (triggered_number > 0)
|
||||
++times_triggered;
|
||||
|
||||
if (triggered_number > 0 && triggered_number <= times_triggered)
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "Triggered number for trap %d reached. %d/%d", trap_id, times_triggered, triggered_number);
|
||||
update = true;
|
||||
}
|
||||
|
||||
if (update)
|
||||
{
|
||||
UpdateTrap();
|
||||
}
|
||||
}
|
||||
|
||||
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) {
|
||||
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist)
|
||||
{
|
||||
float dist = 999999;
|
||||
Trap* current_trap = nullptr;
|
||||
|
||||
@@ -231,45 +279,135 @@ Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) {
|
||||
return current_trap;
|
||||
}
|
||||
|
||||
Mob* EntityList::GetTrapTrigger(Trap* trap) {
|
||||
Mob* savemob = 0;
|
||||
Mob* EntityList::GetTrapTrigger(Trap* trap)
|
||||
{
|
||||
|
||||
float maxdist = trap->radius * trap->radius;
|
||||
|
||||
for (auto it = client_list.begin(); it != client_list.end(); ++it) {
|
||||
for (auto it = client_list.begin(); it != client_list.end(); ++it)
|
||||
{
|
||||
Client* cur = it->second;
|
||||
|
||||
auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position;
|
||||
diff.z = std::abs(diff.z);
|
||||
|
||||
if ((diff.x*diff.x + diff.y*diff.y) <= maxdist
|
||||
&& diff.z < trap->maxzdiff)
|
||||
&& diff.z <= trap->maxzdiff)
|
||||
{
|
||||
if (zone->random.Roll(trap->chance))
|
||||
return(cur);
|
||||
else
|
||||
savemob = cur;
|
||||
}
|
||||
//This prevents the trap from triggering on players while zoning.
|
||||
if (strcmp(cur->GetName(), "No name") == 0)
|
||||
continue;
|
||||
|
||||
if (cur->trapid == 0 && !cur->GetGM() && (trap->chance == 0 || zone->random.Roll(trap->chance)))
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "%s is about to trigger trap %d of chance %d. diff: %0.2f maxdist: %0.2f zdiff: %0.2f maxzdiff: %0.2f", cur->GetName(), trap->trap_id, trap->chance, (diff.x*diff.x + diff.y*diff.y), maxdist, diff.z, trap->maxzdiff);
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cur->trapid == trap->trap_id)
|
||||
{
|
||||
Log(Logs::General, Logs::Traps, "%s is clearing trapid for trap %d", cur->GetName(), trap->trap_id);
|
||||
cur->trapid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return savemob;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//todo: rewrite this to not need direct access to trap members.
|
||||
bool EntityList::IsTrapGroupSpawned(uint32 trap_id, uint8 group)
|
||||
{
|
||||
auto it = trap_list.begin();
|
||||
while (it != trap_list.end())
|
||||
{
|
||||
Trap* cur = it->second;
|
||||
if (cur->IsTrap() && cur->group == group && cur->trap_id != trap_id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EntityList::UpdateAllTraps(bool respawn, bool repopnow)
|
||||
{
|
||||
auto it = trap_list.begin();
|
||||
while (it != trap_list.end())
|
||||
{
|
||||
Trap* cur = it->second;
|
||||
if (cur->IsTrap())
|
||||
{
|
||||
cur->UpdateTrap(respawn, repopnow);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
Log(Logs::General, Logs::Traps, "All traps updated.");
|
||||
}
|
||||
|
||||
void EntityList::GetTrapInfo(Client* client)
|
||||
{
|
||||
uint8 count = 0;
|
||||
auto it = trap_list.begin();
|
||||
while (it != trap_list.end())
|
||||
{
|
||||
Trap* cur = it->second;
|
||||
if (cur->IsTrap())
|
||||
{
|
||||
bool isset = (cur->chkarea_timer.Enabled() && !cur->reset_timer.Enabled());
|
||||
client->Message(CC_Default, " Trap: (%d) found at %0.2f,%0.2f,%0.2f. Times Triggered: %d Is Active: %d Group: %d Message: %s", cur->trap_id, cur->m_Position.x, cur->m_Position.y, cur->m_Position.z, cur->times_triggered, isset, cur->group, cur->message.c_str());
|
||||
++count;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
client->Message(CC_Default, "%d traps found.", count);
|
||||
}
|
||||
|
||||
void EntityList::ClearTrapPointers()
|
||||
{
|
||||
auto it = trap_list.begin();
|
||||
while (it != trap_list.end())
|
||||
{
|
||||
Trap* cur = it->second;
|
||||
if (cur->IsTrap())
|
||||
{
|
||||
cur->DestroyHiddenTrigger();
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
|
||||
|
||||
std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
|
||||
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level "
|
||||
"FROM traps WHERE zone='%s' AND version=%u", zonename, version);
|
||||
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
|
||||
"`group`, triggered_number, despawn_when_triggered FROM traps WHERE zone='%s' AND version=%u", zonename, version);
|
||||
|
||||
auto results = QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
uint32 tid = atoi(row[0]);
|
||||
uint8 grp = atoi(row[15]);
|
||||
|
||||
if (grp > 0)
|
||||
{
|
||||
// If a member of our group is already spawned skip loading this trap.
|
||||
if (entity_list.IsTrapGroupSpawned(tid, grp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto trap = new Trap();
|
||||
trap->trap_id = atoi(row[0]);
|
||||
trap->trap_id = tid;
|
||||
trap->db_id = tid;
|
||||
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
|
||||
trap->effect = atoi(row[4]);
|
||||
trap->effectvalue = atoi(row[5]);
|
||||
@@ -282,8 +420,12 @@ bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
|
||||
trap->respawn_time = atoi(row[12]);
|
||||
trap->respawn_var = atoi(row[13]);
|
||||
trap->level = atoi(row[14]);
|
||||
trap->group = grp;
|
||||
trap->triggered_number = atoi(row[16]);
|
||||
trap->despawn_when_triggered = atoi(row[17]);
|
||||
entity_list.AddTrap(trap);
|
||||
trap->CreateHiddenTrigger();
|
||||
Log(Logs::General, Logs::Traps, "Trap %d successfully loaded.", trap->trap_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -318,3 +460,86 @@ void Trap::CreateHiddenTrigger()
|
||||
hiddenTrigger = npca;
|
||||
ownHiddenTrigger = true;
|
||||
}
|
||||
bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) {
|
||||
|
||||
uint32 dbid = trap->db_id;
|
||||
std::string query;
|
||||
|
||||
if (trap->group > 0)
|
||||
{
|
||||
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
|
||||
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
|
||||
"triggered_number, despawn_when_triggered FROM traps WHERE zone='%s' AND `group`=%d AND id != %d ORDER BY RAND() LIMIT 1", zone->GetShortName(), trap->group, dbid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We could just use the existing data here, but querying the DB is not expensive, and allows content developers to change traps without rebooting.
|
||||
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
|
||||
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
|
||||
"triggered_number, despawn_when_triggered FROM traps WHERE zone='%s' AND id = %d", zone->GetShortName(), dbid);
|
||||
}
|
||||
|
||||
auto results = QueryDatabase(query);
|
||||
if (!results.Success()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
|
||||
trap->db_id = atoi(row[0]);
|
||||
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
|
||||
trap->effect = atoi(row[4]);
|
||||
trap->effectvalue = atoi(row[5]);
|
||||
trap->effectvalue2 = atoi(row[6]);
|
||||
trap->skill = atoi(row[7]);
|
||||
trap->maxzdiff = atof(row[8]);
|
||||
trap->radius = atof(row[9]);
|
||||
trap->chance = atoi(row[10]);
|
||||
trap->message = row[11];
|
||||
trap->respawn_time = atoi(row[12]);
|
||||
trap->respawn_var = atoi(row[13]);
|
||||
trap->level = atoi(row[14]);
|
||||
trap->triggered_number = atoi(row[15]);
|
||||
trap->despawn_when_triggered = atoi(row[16]);
|
||||
trap->CreateHiddenTrigger();
|
||||
|
||||
if (repopnow)
|
||||
{
|
||||
trap->chkarea_timer.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000);
|
||||
}
|
||||
|
||||
if (trap->trap_id != trap->db_id)
|
||||
Log(Logs::General, Logs::Traps, "Trap (%d) DBID has changed from %d to %d", trap->trap_id, dbid, trap->db_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Trap::UpdateTrap(bool respawn, bool repopnow)
|
||||
{
|
||||
respawn_timer.Disable();
|
||||
chkarea_timer.Disable();
|
||||
reset_timer.Disable();
|
||||
if (hiddenTrigger)
|
||||
{
|
||||
hiddenTrigger->Depop();
|
||||
hiddenTrigger = nullptr;
|
||||
}
|
||||
times_triggered = 0;
|
||||
Client* trigger = entity_list.GetClientByCharID(charid);
|
||||
if (trigger)
|
||||
{
|
||||
trigger->trapid = 0;
|
||||
}
|
||||
charid = 0;
|
||||
if (respawn)
|
||||
{
|
||||
database.SetTrapData(this, repopnow);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user