From ed98aa45d2677840bdb7566c358861dc96138e84 Mon Sep 17 00:00:00 2001 From: regneq Date: Fri, 27 Oct 2017 21:24:24 -0700 Subject: [PATCH 1/3] 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 --- common/eqemu_logsys.h | 4 +- utils/sql/git/required/2017_10_28_traps.sql | 3 + zone/client.cpp | 2 + zone/client.h | 2 + zone/client_packet.cpp | 4 +- zone/command.cpp | 12 + zone/command.h | 2 + zone/entity.h | 4 + zone/trap.cpp | 271 ++++++++++++++++++-- zone/trap.h | 13 +- zone/zone.cpp | 7 +- zone/zonedb.h | 3 +- 12 files changed, 297 insertions(+), 30 deletions(-) create mode 100644 utils/sql/git/required/2017_10_28_traps.sql diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 10d9e460c..a54edc234 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -89,6 +89,7 @@ enum LogCategory { HP_Update, FixZ, Food, + Traps, MaxCategoryID /* Don't Remove this*/ }; @@ -142,7 +143,8 @@ static const char* LogCategoryName[LogCategory::MaxCategoryID] = { "Headless Client", "HP Update", "FixZ", - "Food" + "Food", + "Traps" }; } diff --git a/utils/sql/git/required/2017_10_28_traps.sql b/utils/sql/git/required/2017_10_28_traps.sql new file mode 100644 index 000000000..20c249ede --- /dev/null +++ b/utils/sql/git/required/2017_10_28_traps.sql @@ -0,0 +1,3 @@ +alter table `traps` add column `triggered_number` tinyint(4) not null default 0; +alter table `traps` add column `group` tinyint(4) not null default 0; +alter table `traps` add column `despawn_when_triggered` tinyint(4) not null default 0; diff --git a/zone/client.cpp b/zone/client.cpp index ce084747a..893a6b51b 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -331,6 +331,8 @@ Client::Client(EQStreamInterface* ieqs) interrogateinv_flag = false; + trapid = 0; + for (int i = 0; i < InnateSkillMax; ++i) m_pp.InnateSkills[i] = InnateDisabled; diff --git a/zone/client.h b/zone/client.h index e334f8fd9..88622fcff 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1302,6 +1302,8 @@ public: int32 CalcATK(); + uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns. + protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 6710fdfec..4271ec33f 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5337,8 +5337,8 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) { Message(MT_Skills, "You disarm a trap."); trap->disarmed = true; - trap->chkarea_timer.Disable(); - trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); + Log(Logs::General, Logs::Traps, "Trap %d is disarmed.", trap->trap_id); + trap->UpdateTrap(); } else { diff --git a/zone/command.cpp b/zone/command.cpp index 15b09b9dc..a65f9005e 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -321,6 +321,7 @@ int command_init(void) command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) || command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) || command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) || + command_add("reloadtraps", "- Repops all traps in the current zone.", 80, command_reloadtraps) || command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) || command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) || command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) || @@ -382,6 +383,7 @@ int command_init(void) command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) || command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) || command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) || + command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) || command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) || command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) || command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) || @@ -10851,6 +10853,16 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep) } } +void command_trapinfo(Client *c, const Seperator *sep) +{ + entity_list.GetTrapInfo(c); +} + +void command_reloadtraps(Client *c, const Seperator *sep) +{ + entity_list.UpdateAllTraps(true, true); + c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName()); +} // All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block. #ifdef BOTS diff --git a/zone/command.h b/zone/command.h index 0a850fbca..dca70d767 100644 --- a/zone/command.h +++ b/zone/command.h @@ -228,6 +228,7 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep); void command_reloadqst(Client *c, const Seperator *sep); void command_reloadstatic(Client *c, const Seperator *sep); void command_reloadtitles(Client *c, const Seperator *sep); +void command_reloadtraps(Client* c, const Seperator *sep); void command_reloadworld(Client *c, const Seperator *sep); void command_reloadworldrules(Client *c, const Seperator *sep); void command_reloadzps(Client *c, const Seperator *sep); @@ -295,6 +296,7 @@ void command_timezone(Client *c, const Seperator *sep); void command_title(Client *c, const Seperator *sep); void command_titlesuffix(Client *c, const Seperator *sep); void command_traindisc(Client *c, const Seperator *sep); +void command_trapinfo(Client* c, const Seperator *sep); void command_tune(Client *c, const Seperator *sep); void command_undye(Client *c, const Seperator *sep); void command_undyeme(Client *c, const Seperator *sep); diff --git a/zone/entity.h b/zone/entity.h index 29075d9fe..996580aa6 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -473,6 +473,10 @@ public: void RefreshClientXTargets(Client *c); void SendAlternateAdvancementStats(); + void GetTrapInfo(Client* client); + bool IsTrapGroupSpawned(uint32 trap_id, uint8 group); + void UpdateAllTraps(bool respawn, bool repopnow = false); + void ClearTrapPointers(); protected: friend class Zone; void Depop(bool StartSpawnTimer = false); diff --git a/zone/trap.cpp b/zone/trap.cpp index 87000f940..f563048f8 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -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); + } +} \ No newline at end of file diff --git a/zone/trap.h b/zone/trap.h index 4adab320a..4c0d53106 100644 --- a/zone/trap.h +++ b/zone/trap.h @@ -49,11 +49,15 @@ public: NPC * GetHiddenTrigger() { return hiddenTrigger; } void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; } void CreateHiddenTrigger(); - + void DestroyHiddenTrigger() { hiddenTrigger = nullptr; } + void SetTrapData(); + void UpdateTrap(bool respawn = true, bool repopnow = false); //Trap data, leave this unprotected Timer respawn_timer; //Respawn Time when Trap's been disarmed Timer chkarea_timer; - uint32 trap_id; //Database ID of trap + Timer reset_timer; //How long a trap takes to reset before triggering again. + uint32 trap_id; //Original ID of the trap from DB. This value never changes. + uint32 db_id; //The DB ID of the trap that currently is spawned. glm::vec3 m_Position; float maxzdiff; //maximum z diff to be triggerable float radius; //radius around trap to be triggerable @@ -67,6 +71,11 @@ public: bool disarmed; uint32 respawn_time; uint32 respawn_var; + uint8 triggered_number; + uint8 times_triggered; + uint8 group; + bool despawn_when_triggered; + uint32 charid; //ID of character that triggered trap. This is cleared when the trap despawns are resets. std::string message; protected: diff --git a/zone/zone.cpp b/zone/zone.cpp index 8441f7d82..3a8f1e938 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1428,7 +1428,8 @@ void Zone::StartShutdownTimer(uint32 set_time) { bool Zone::Depop(bool StartSpawnTimer) { std::map::iterator itr; entity_list.Depop(StartSpawnTimer); - + entity_list.ClearTrapPointers(); + entity_list.UpdateAllTraps(false); /* Refresh npctable (cache), getting current info from database. */ while(!npctable.empty()) { itr = npctable.begin(); @@ -1496,6 +1497,8 @@ void Zone::Repop(uint32 delay) { iterator.RemoveCurrent(); } + entity_list.ClearTrapPointers(); + quest_manager.ClearAllTimers(); if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay)) @@ -1503,6 +1506,8 @@ void Zone::Repop(uint32 delay) { initgrids_timer.Start(); + entity_list.UpdateAllTraps(true, true); + //MODDING HOOK FOR REPOP mod_repop(); } diff --git a/zone/zonedb.h b/zone/zonedb.h index a8ab3659f..c134dade3 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -16,6 +16,7 @@ class NPC; class Petition; class Spawn2; class SpawnGroupList; +class Trap; struct CharacterEventLog_Struct; struct Door; struct ExtendedProfile_Struct; @@ -478,7 +479,7 @@ public: /* Traps */ bool LoadTraps(const char* zonename, int16 version); - char* GetTrapMessage(uint32 trap_id); + bool SetTrapData(Trap* trap, bool repopnow = false); /* Time */ uint32 GetZoneTZ(uint32 zoneid, uint32 version); From cd748e7d8bb865298d9b58963a75a6766f11411b Mon Sep 17 00:00:00 2001 From: regneq Date: Sat, 28 Oct 2017 10:02:31 -0700 Subject: [PATCH 2/3] Fixed an issue that would cause traps to not function correctly if skill is 0 in the database. Added undetectable column, to allow content developers to make a trap undetectable and not able to be disarmed. Pets will no longer try to aggro traps its owner triggers. Traps will now use the radius column to determine disarm range, instead of using a hardcoded value which may not be appropriate in all cases. Decreased the scan range for traps to disarm. Fixed some typos, and removed some unused code. --- utils/sql/git/required/2017_10_28_traps.sql | 1 + zone/attack.cpp | 5 ++- zone/client_packet.cpp | 50 +++++++++++++-------- zone/common.h | 5 +++ zone/entity.h | 2 +- zone/string_ids.h | 5 +++ zone/trap.cpp | 31 +++++++++---- zone/trap.h | 2 +- 8 files changed, 71 insertions(+), 30 deletions(-) diff --git a/utils/sql/git/required/2017_10_28_traps.sql b/utils/sql/git/required/2017_10_28_traps.sql index 20c249ede..dddc00d1b 100644 --- a/utils/sql/git/required/2017_10_28_traps.sql +++ b/utils/sql/git/required/2017_10_28_traps.sql @@ -1,3 +1,4 @@ alter table `traps` add column `triggered_number` tinyint(4) not null default 0; alter table `traps` add column `group` tinyint(4) not null default 0; alter table `traps` add column `despawn_when_triggered` tinyint(4) not null default 0; +alter table `traps` add column `undetectable` tinyint(4) not null default 0; diff --git a/zone/attack.cpp b/zone/attack.cpp index a6baffc8d..d12d8d36c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2533,6 +2533,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b if (other == this) return; + if (other->IsTrap()) + return; + if (damage < 0) { hate = 1; } @@ -3364,7 +3367,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const // pets that have GHold will never automatically add NPCs // pets that have Hold and no Focus will add NPCs if they're engaged // pets that have Hold and Focus will not add NPCs - if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld()) + if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap()) { if (!pet->IsHeld()) { Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName()); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 4271ec33f..8efe55f2b 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5329,31 +5329,44 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app) p_timers.Start(pTimerDisarmTraps, reuse - 1); - Trap* trap = entity_list.FindNearbyTrap(this, 60); + uint8 success = SKILLUP_FAILURE; + float curdist = 0; + Trap* trap = entity_list.FindNearbyTrap(this, 250, curdist, true); if (trap && trap->detected) { - int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps); - if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) + float max_radius = (trap->radius * 2) * (trap->radius * 2); // radius is used to trigger trap, so disarm radius should be a bit bigger. + Log(Logs::General, Logs::Traps, "%s is attempting to disarm trap %d. Curdist is %0.2f maxdist is %0.2f", GetName(), trap->trap_id, curdist, max_radius); + if (curdist <= max_radius) { - Message(MT_Skills, "You disarm a trap."); - trap->disarmed = true; - Log(Logs::General, Logs::Traps, "Trap %d is disarmed.", trap->trap_id); - trap->UpdateTrap(); + int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps); + if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill)) + { + success = SKILLUP_SUCCESS; + Message_StringID(MT_Skills, DISARMED_TRAP); + trap->disarmed = true; + Log(Logs::General, Logs::Traps, "Trap %d is disarmed.", trap->trap_id); + trap->UpdateTrap(); + } + else + { + Message_StringID(MT_Skills, FAIL_DISARM_DETECTED_TRAP); + if (zone->random.Int(0, 99) < 25) { + trap->Trigger(this); + } + } + CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr); + return; } else { - if (zone->random.Int(0, 99) < 25) { - Message(MT_Skills, "You set off the trap while trying to disarm it!"); - trap->Trigger(this); - } - else { - Message(MT_Skills, "You failed to disarm a trap."); - } + Message_StringID(MT_Skills, TRAP_TOO_FAR); } - CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr); - return; } - Message(MT_Skills, "You did not find any traps close enough to disarm."); + else + { + Message_StringID(MT_Skills, LDON_SENSE_TRAP2); + } + return; } @@ -12107,7 +12120,8 @@ void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app) p_timers.Start(pTimerSenseTraps, reuse - 1); - Trap* trap = entity_list.FindNearbyTrap(this, 800); + float trap_curdist = 0; + Trap* trap = entity_list.FindNearbyTrap(this, 800, trap_curdist); CheckIncreaseSkill(EQEmu::skills::SkillSenseTraps, nullptr); diff --git a/zone/common.h b/zone/common.h index bcc9ff305..f7b157115 100644 --- a/zone/common.h +++ b/zone/common.h @@ -604,6 +604,11 @@ enum { //type arguments to DoAnim }; +enum { + SKILLUP_UNKNOWN = 0, + SKILLUP_SUCCESS = 1, + SKILLUP_FAILURE = 2 +}; typedef enum { petFamiliar, //only listens to /pet get lost diff --git a/zone/entity.h b/zone/entity.h index 996580aa6..dccbb45b3 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -365,7 +365,7 @@ public: //trap stuff Mob* GetTrapTrigger(Trap* trap); void SendAlarm(Trap* trap, Mob* currenttarget, uint8 kos); - Trap* FindNearbyTrap(Mob* searcher, float max_dist); + Trap* FindNearbyTrap(Mob* searcher, float max_dist, float &curdist, bool detected = false); void AddHealAggro(Mob* target, Mob* caster, uint16 hate); Mob* FindDefenseNPC(uint32 npcid); diff --git a/zone/string_ids.h b/zone/string_ids.h index c8175c8b6..997858bed 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -19,6 +19,7 @@ #define PROC_TOOLOW 126 //Your will is not sufficient to command this weapon. #define PROC_PETTOOLOW 127 //Your pet's will is not sufficient to command its weapon. #define YOU_FLURRY 128 //You unleash a flurry of attacks. +#define FAILED_DISARM_TRAP 129 //You failed to disarm the trap. #define DOORS_LOCKED 130 //It's locked and you're not holding the key. #define DOORS_CANT_PICK 131 //This lock cannot be picked. #define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock. @@ -98,6 +99,7 @@ #define DUP_LORE 290 //Duplicate lore items are not allowed. #define TGB_ON 293 //Target other group buff is *ON*. #define TGB_OFF 294 //Target other group buff is *OFF*. +#define DISARMED_TRAP 305 //You have disarmed the trap. #define LDON_SENSE_TRAP1 306 //You do not Sense any traps. #define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type! #define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together. @@ -114,6 +116,8 @@ #define MEND_WORSEN 351 //You have worsened your wounds! #define MEND_FAIL 352 //You have failed to mend your wounds. #define LDON_SENSE_TRAP2 367 //You have not detected any traps. +#define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it. +#define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap. #define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one. #define PICK_LORE 379 //You cannot pick up a lore item you already possess. #define CONSENT_DENIED 390 //You do not have consent to summon that corpse. @@ -421,6 +425,7 @@ #define SENSE_ANIMAL 12472 //You sense an animal in this direction. #define SENSE_SUMMONED 12473 //You sense a summoned being in this direction. #define SENSE_NOTHING 12474 //You don't sense anything. +#define SENSE_TRAP 12475 //You sense a trap in this direction. #define LDON_SENSE_TRAP3 12476 //You don't sense any traps. #define INTERRUPT_SPELL_OTHER 12478 //%1's casting is interrupted! #define YOU_HIT_NONMELEE 12481 //You were hit by non-melee for %1 damage. diff --git a/zone/trap.cpp b/zone/trap.cpp index f563048f8..d5bcfbeb8 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -79,6 +79,7 @@ Trap::Trap() : group = 0; despawn_when_triggered = false; charid = 0; + undetectable = false; } Trap::~Trap() @@ -253,7 +254,7 @@ void Trap::Trigger(Mob* trigger) } } -Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) +Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist, float &trap_curdist, bool detected) { float dist = 999999; Trap* current_trap = nullptr; @@ -263,19 +264,29 @@ Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) for (auto it = trap_list.begin(); it != trap_list.end(); ++it) { cur = it->second; - if(cur->disarmed) + if(cur->disarmed || (detected && !cur->detected) || cur->undetectable) continue; auto diff = glm::vec3(searcher->GetPosition()) - cur->m_Position; - float curdist = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z; + float curdist = diff.x*diff.x + diff.y*diff.y; + diff.z = std::abs(diff.z); - if (curdist < max_dist2 && curdist < dist) + if (curdist < max_dist2 && curdist < dist && diff.z <= cur->maxzdiff) { + Log(Logs::General, Logs::Traps, "Trap %d is curdist %0.1f", cur->db_id, curdist); dist = curdist; current_trap = cur; } } + if (current_trap != nullptr) + { + Log(Logs::General, Logs::Traps, "Trap %d is the closest trap.", current_trap->db_id); + trap_curdist = dist; + } + else + trap_curdist = INVALID_INDEX; + return current_trap; } @@ -386,7 +397,7 @@ 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, " - "`group`, triggered_number, despawn_when_triggered FROM traps WHERE zone='%s' AND version=%u", zonename, version); + "`group`, triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND version=%u", zonename, version); auto results = QueryDatabase(query); if (!results.Success()) { @@ -422,7 +433,8 @@ bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) { trap->level = atoi(row[14]); trap->group = grp; trap->triggered_number = atoi(row[16]); - trap->despawn_when_triggered = atoi(row[17]); + trap->despawn_when_triggered = atobool(row[17]); + trap->undetectable = atobool(row[18]); entity_list.AddTrap(trap); trap->CreateHiddenTrigger(); Log(Logs::General, Logs::Traps, "Trap %d successfully loaded.", trap->trap_id); @@ -469,14 +481,14 @@ bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) { { 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); + "triggered_number, despawn_when_triggered, undetectable 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); + "triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND id = %d", zone->GetShortName(), dbid); } auto results = QueryDatabase(query); @@ -500,7 +512,8 @@ bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) { 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->despawn_when_triggered = atobool(row[16]); + trap->undetectable = atobool(row[17]); trap->CreateHiddenTrigger(); if (repopnow) diff --git a/zone/trap.h b/zone/trap.h index 4c0d53106..e20e2314f 100644 --- a/zone/trap.h +++ b/zone/trap.h @@ -50,7 +50,6 @@ public: void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; } void CreateHiddenTrigger(); void DestroyHiddenTrigger() { hiddenTrigger = nullptr; } - void SetTrapData(); void UpdateTrap(bool respawn = true, bool repopnow = false); //Trap data, leave this unprotected Timer respawn_timer; //Respawn Time when Trap's been disarmed @@ -76,6 +75,7 @@ public: uint8 group; bool despawn_when_triggered; uint32 charid; //ID of character that triggered trap. This is cleared when the trap despawns are resets. + bool undetectable; std::string message; protected: From 15f7440af24d0d52e381b707d5ad8b163ed0b56e Mon Sep 17 00:00:00 2001 From: regneq Date: Sat, 28 Oct 2017 10:48:22 -0700 Subject: [PATCH 3/3] Update version and manifest for traps.sql --- common/version.h | 2 +- utils/sql/db_update_manifest.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/version.h b/common/version.h index 52af87872..108ce784b 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9114 +#define CURRENT_BINARY_DATABASE_VERSION 9115 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9017 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index e385b9f9e..c9b6a7b78 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -368,6 +368,7 @@ 9112|2017_06_24_rule_values_expand.sql|SHOW COLUMNS FROM rule_values WHERE Field = 'rule_value' and Type = 'varchar(30)'|empty| 9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty| 9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|empty| +9115|2017_10_28_traps.sql|SHOW COLUMNS FROM `traps` LIKE 'triggered_number'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not