diff --git a/common/emu_legacy.h b/common/emu_legacy.h index a624be883..0f3cf0a62 100644 --- a/common/emu_legacy.h +++ b/common/emu_legacy.h @@ -78,6 +78,8 @@ namespace EQEmu SLOT_CURSOR_BAG_END = 340, SLOT_TRIBUTE_BEGIN = 400, SLOT_TRIBUTE_END = 404, + SLOT_GUILD_TRIBUTE_BEGIN = 450, + SLOT_GUILD_TRIBUTE_END = 451, SLOT_BANK_BEGIN = 2000, SLOT_BANK_END = 2023, SLOT_BANK_BAGS_BEGIN = 2031, diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 4876b4211..8452813f5 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -4767,6 +4767,7 @@ struct BuffIconEntry_Struct uint32 spell_id; int32 tics_remaining; uint32 num_hits; + char caster[64]; }; struct BuffIcon_Struct @@ -4776,6 +4777,7 @@ struct BuffIcon_Struct uint16 count; uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC int32 tic_timer; + int32 name_lengths; // so ahh we kind of do these packets hacky, this is the total length of all the names to make creating the real packets in the translators easier BuffIconEntry_Struct entries[0]; }; 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/common/patches/rof.cpp b/common/patches/rof.cpp index 1df8d1aa5..b655cffb0 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -460,7 +460,7 @@ namespace RoF { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -476,7 +476,7 @@ namespace RoF __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); // Unknown diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 57a8d9eb6..baf6d6b09 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -528,7 +528,7 @@ namespace RoF2 { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -544,7 +544,7 @@ namespace RoF2 __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); // Unknown diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 87cd6c700..cf0c17176 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -391,7 +391,7 @@ namespace UF { SETUP_VAR_ENCODE(BuffIcon_Struct); - uint32 sz = 12 + (17 * emu->count); + uint32 sz = 12 + (17 * emu->count) + emu->name_lengths; // 17 includes nullterm __packet->size = sz; __packet->pBuffer = new unsigned char[sz]; memset(__packet->pBuffer, 0, sz); @@ -407,7 +407,7 @@ namespace UF __packet->WriteUInt32(emu->entries[i].spell_id); __packet->WriteUInt32(emu->entries[i].tics_remaining); __packet->WriteUInt32(emu->entries[i].num_hits); - __packet->WriteString(""); + __packet->WriteString(emu->entries[i].caster); } __packet->WriteUInt8(emu->type); diff --git a/common/ruletypes.h b/common/ruletypes.h index c3a8fd5dc..ed5ae8e23 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -44,6 +44,7 @@ RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this wi RULE_INT(Character, DeathItemLossLevel, 10) RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default. +RULE_BOOL(Character, UseOldRaceRezEffects, false) // older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore RULE_INT(Character, CorpseDecayTimeMS, 10800000) RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours) RULE_BOOL(Character, LeaveCorpses, true) 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 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..dddc00d1b --- /dev/null +++ b/utils/sql/git/required/2017_10_28_traps.sql @@ -0,0 +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/world/adventure.cpp b/world/adventure.cpp index cbea9079c..c984fa831 100644 --- a/world/adventure.cpp +++ b/world/adventure.cpp @@ -380,7 +380,7 @@ void Adventure::MoveCorpsesToGraveyard() std::list dbid_list; std::list charid_list; - std::string query = StringFormat("SELECT id, charid FROM character_corpses WHERE instanceid=%d", GetInstanceID()); + std::string query = StringFormat("SELECT id, charid FROM character_corpses WHERE instance_id=%d", GetInstanceID()); auto results = database.QueryDatabase(query); if(!results.Success()) @@ -395,8 +395,8 @@ void Adventure::MoveCorpsesToGraveyard() float z = GetTemplate()->graveyard_z; query = StringFormat("UPDATE character_corpses " - "SET zoneid = %d, instanceid = 0, " - "x = %f, y = %f, z = %f WHERE instanceid = %d", + "SET zone_id = %d, instance_id = 0, " + "x = %f, y = %f, z = %f WHERE instance_id = %d", GetTemplate()->graveyard_zone_id, x, y, z, GetInstanceID()); database.QueryDatabase(query); diff --git a/zone/attack.cpp b/zone/attack.cpp index a87f504b7..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()); @@ -3922,10 +3925,10 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData * float WPC = ProcChance * (100.0f + // Proc chance for this weapon static_cast(weapon->ProcRate)) / 100.0f; if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really. - if (weapon->Proc.Level > ourlevel) { + if (weapon->Proc.Level2 > ourlevel) { Log(Logs::Detail, Logs::Combat, "Tried to proc (%s), but our level (%d) is lower than required (%d)", - weapon->Name, ourlevel, weapon->Proc.Level); + weapon->Name, ourlevel, weapon->Proc.Level2); if (IsPet()) { Mob *own = GetOwner(); if (own) @@ -3962,7 +3965,7 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData * float APC = ProcChance * (100.0f + // Proc chance for this aug static_cast(aug->ProcRate)) / 100.0f; if (zone->random.Roll(APC)) { - if (aug->Proc.Level > ourlevel) { + if (aug->Proc.Level2 > ourlevel) { if (IsPet()) { Mob *own = GetOwner(); if (own) diff --git a/zone/client.cpp b/zone/client.cpp index ce084747a..319752e36 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; @@ -1221,11 +1223,6 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num EffSkill = 100; cm->skill_in_language = EffSkill; - // Garble the message based on listener skill - if (ListenerSkill < 100) { - GarbleMessage(buffer, (100 - ListenerSkill)); - } - cm->chan_num = chan_num; strcpy(&cm->message[0], buffer); QueuePacket(&app); 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 176efee98..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; - trap->chkarea_timer.Disable(); - trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000); + 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; } @@ -11480,7 +11493,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) Client *client_moved = entity_list.GetClientByName(raid_command_packet->leader_name); - if (client_moved) { + if (client_moved && client_moved->GetRaid()) { client_moved->GetRaid()->SendHPManaEndPacketsTo(client_moved); client_moved->GetRaid()->SendHPManaEndPacketsFrom(client_moved); @@ -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/client_process.cpp b/zone/client_process.cpp index a0bdeead2..355dd2c1f 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -269,8 +269,17 @@ bool Client::Process() { } } - if (force_spawn_updates && mob != this && distance <= client_update_range) - mob->SendPositionUpdateToClient(this); + if (force_spawn_updates && mob != this) { + + if (mob->is_distance_roamer) { + Log(Logs::General, Logs::Debug, "Updating distance roamer %s", mob->GetCleanName()); + mob->SendPositionUpdateToClient(this); + continue; + } + + if (distance <= client_update_range) + mob->SendPositionUpdateToClient(this); + } } } @@ -1055,7 +1064,8 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I SetMana(0); SetHP(GetMaxHP()/5); int rez_eff = 756; - if (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE) + if (RuleB(Character, UseOldRaceRezEffects) && + (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE)) rez_eff = 757; SpellOnTarget(rez_eff, this); // Rezz effects } diff --git a/zone/command.cpp b/zone/command.cpp index d275c82fc..db24d3185 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) || @@ -383,6 +384,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) || @@ -10872,6 +10874,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 084286e4a..a27f173fc 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); @@ -296,6 +297,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/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 29075d9fe..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); @@ -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/mob.cpp b/zone/mob.cpp index 28ee296ee..9157b2fe8 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -75,7 +75,6 @@ Mob::Mob(const char* in_name, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, EQEmu::TintProfile in_armor_tint, - uint8 in_aa_title, uint8 in_see_invis, // see through invis/ivu uint8 in_see_invis_undead, @@ -91,24 +90,24 @@ Mob::Mob(const char* in_name, uint8 in_handtexture, uint8 in_legtexture, uint8 in_feettexture - ) : + ) : attack_timer(2000), attack_dw_timer(2000), ranged_timer(2000), tic_timer(6000), mana_timer(2000), spellend_timer(0), - rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind. + rewind_timer(30000), bindwound_timer(10000), stunned_timer(0), spun_timer(0), bardsong_timer(6000), gravity_timer(1000), viral_timer(0), - m_FearWalkTarget(-999999.0f,-999999.0f,-999999.0f), + m_FearWalkTarget(-999999.0f, -999999.0f, -999999.0f), m_TargetLocation(glm::vec3()), m_TargetV(glm::vec3()), - flee_timer(FLEE_CHECK_TIMER), + flee_timer(FLEE_CHECK_TIMER), m_Position(position), tmHidden(-1), mitigation_ac(0), @@ -119,45 +118,46 @@ Mob::Mob(const char* in_name, position_update_melee_push_timer(1000) { targeted = 0; - tar_ndx=0; - tar_vector=0; + tar_ndx = 0; + tar_vector = 0; currently_fleeing = false; last_major_update_position = m_Position; + is_distance_roamer = false; AI_Init(); SetMoving(false); - moved=false; + moved = false; m_RewindLocation = glm::vec3(); _egnode = nullptr; - name[0]=0; - orig_name[0]=0; - clean_name[0]=0; - lastname[0]=0; - if(in_name) { - strn0cpy(name,in_name,64); - strn0cpy(orig_name,in_name,64); + name[0] = 0; + orig_name[0] = 0; + clean_name[0] = 0; + lastname[0] = 0; + if (in_name) { + strn0cpy(name, in_name, 64); + strn0cpy(orig_name, in_name, 64); } - if(in_lastname) - strn0cpy(lastname,in_lastname,64); - cur_hp = in_cur_hp; - max_hp = in_max_hp; - base_hp = in_max_hp; - gender = in_gender; - race = in_race; - base_gender = in_gender; - base_race = in_race; - class_ = in_class; - bodytype = in_bodytype; + if (in_lastname) + strn0cpy(lastname, in_lastname, 64); + cur_hp = in_cur_hp; + max_hp = in_max_hp; + base_hp = in_max_hp; + gender = in_gender; + race = in_race; + base_gender = in_gender; + base_race = in_race; + class_ = in_class; + bodytype = in_bodytype; orig_bodytype = in_bodytype; - deity = in_deity; - level = in_level; + deity = in_deity; + level = in_level; orig_level = in_level; - npctype_id = in_npctype_id; - size = in_size; - base_size = size; - runspeed = in_runspeed; + npctype_id = in_npctype_id; + size = in_size; + base_size = size; + runspeed = in_runspeed; // neotokyo: sanity check if (runspeed < 0 || runspeed > 20) runspeed = 1.25f; @@ -170,7 +170,8 @@ Mob::Mob(const char* in_name, fearspeed = 0.625f; base_fearspeed = 25; // npcs - } else { + } + else { base_walkspeed = base_runspeed * 100 / 265; walkspeed = ((float)base_walkspeed) * 0.025f; base_fearspeed = base_runspeed * 100 / 127; @@ -182,7 +183,7 @@ Mob::Mob(const char* in_name, current_speed = base_runspeed; - m_PlayerState = 0; + m_PlayerState = 0; // sanity check @@ -194,8 +195,8 @@ Mob::Mob(const char* in_name, m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate]; m_Light.Level[EQEmu::lightsource::LightActive] = m_Light.Level[EQEmu::lightsource::LightInnate]; - texture = in_texture; - helmtexture = in_helmtexture; + texture = in_texture; + helmtexture = in_helmtexture; armtexture = in_armtexture; bracertexture = in_bracertexture; handtexture = in_handtexture; @@ -203,21 +204,21 @@ Mob::Mob(const char* in_name, feettexture = in_feettexture; multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture); - haircolor = in_haircolor; - beardcolor = in_beardcolor; - eyecolor1 = in_eyecolor1; - eyecolor2 = in_eyecolor2; - hairstyle = in_hairstyle; - luclinface = in_luclinface; - beard = in_beard; - drakkin_heritage = in_drakkin_heritage; - drakkin_tattoo = in_drakkin_tattoo; - drakkin_details = in_drakkin_details; + haircolor = in_haircolor; + beardcolor = in_beardcolor; + eyecolor1 = in_eyecolor1; + eyecolor2 = in_eyecolor2; + hairstyle = in_hairstyle; + luclinface = in_luclinface; + beard = in_beard; + drakkin_heritage = in_drakkin_heritage; + drakkin_tattoo = in_drakkin_tattoo; + drakkin_details = in_drakkin_details; attack_speed = 0; attack_delay = 0; slow_mitigation = 0; - findable = false; - trackable = true; + findable = false; + trackable = true; has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; @@ -228,19 +229,19 @@ Mob::Mob(const char* in_name, SpellPowerDistanceMod = 0; last_los_check = false; - if(in_aa_title>0) - aa_title = in_aa_title; + if (in_aa_title > 0) + aa_title = in_aa_title; else - aa_title =0xFF; - AC = in_ac; - ATK = in_atk; - STR = in_str; - STA = in_sta; - DEX = in_dex; - AGI = in_agi; - INT = in_int; - WIS = in_wis; - CHA = in_cha; + aa_title = 0xFF; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; MR = CR = FR = DR = PR = Corrup = 0; ExtraHaste = 0; @@ -261,8 +262,8 @@ Mob::Mob(const char* in_name, hidden = false; improved_hidden = false; invulnerable = false; - IsFullHP = (cur_hp == max_hp); - qglobal=0; + IsFullHP = (cur_hp == max_hp); + qglobal = 0; spawned = false; InitializeBuffSlots(); @@ -303,7 +304,7 @@ Mob::Mob(const char* in_name, logging_enabled = false; isgrouped = false; israidgrouped = false; - + IsHorse = false; entity_id_being_looted = 0; @@ -374,13 +375,13 @@ Mob::Mob(const char* in_name, } destructibleobject = false; - wandertype=0; - pausetype=0; + wandertype = 0; + pausetype = 0; cur_wp = 0; m_CurrentWayPoint = glm::vec4(); cur_wp_pause = 0; - patrol=0; - follow=0; + patrol = 0; + follow = 0; follow_dist = 100; // Default Distance for Follow no_target_hotkey = false; flee_mode = false; @@ -394,7 +395,7 @@ Mob::Mob(const char* in_name, rooted = false; charmed = false; has_virus = false; - for (i=0; i= (100 * 100)) { entity_list.QueueClients(this, app, true, true); last_major_update_position = m_Position; + is_distance_roamer = true; } else { entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false); @@ -3395,13 +3397,18 @@ int Mob::GetHaste() else // 1-25 h += itembonuses.haste > 10 ? 10 : itembonuses.haste; - // 60+ 100, 51-59 85, 1-50 level+25 - if (level > 59) // 60+ - cap = RuleI(Character, HasteCap); - else if (level > 50) // 51-59 - cap = 85; - else // 1-50 - cap = level + 25; + // mobs are different! + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (IsNPC() && CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + if (owner) { + cap = 10 + level; + cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60); + } else { + cap = 150; + } if(h > cap) h = cap; @@ -3960,10 +3967,17 @@ int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) { bool Mob::TryFadeEffect(int slot) { + if (!buffs[slot].spellid) + return false; + if(IsValidSpell(buffs[slot].spellid)) { for(int i = 0; i < EFFECT_COUNT; i++) { + + if (!spells[buffs[slot].spellid].effectid[i]) + continue; + if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways || spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect) { diff --git a/zone/mob.h b/zone/mob.h index 812f54ca1..e3dbb8877 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,6 +162,8 @@ public: inline virtual bool IsMob() const { return true; } inline virtual bool InZone() const { return true; } + bool is_distance_roamer; + //Somewhat sorted: needs documenting! //Attack @@ -955,7 +957,7 @@ public: void SendToFixZ(float new_x, float new_y, float new_z); float GetZOffset() const; void FixZ(int32 z_find_offset = 5); - float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5); + float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } @@ -1224,7 +1226,8 @@ protected: uint32 npctype_id; glm::vec4 m_Position; /* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */ - glm::vec4 last_major_update_position; + glm::vec4 last_major_update_position; + int animation; // this is really what MQ2 calls SpeedRun just packed like (int)(SpeedRun * 40.0f) float base_size; float size; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index eb045488d..e42e4f617 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1298,7 +1298,7 @@ void Mob::AI_Process() { if (AI_PursueCastCheck()) { //we did something, so do not process movement. } - else if (AI_movement_timer->Check()) + else if (AI_movement_timer->Check() && target) { if (!IsRooted()) { Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName()); diff --git a/zone/net.cpp b/zone/net.cpp index eafdde807..80411af7b 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -542,8 +542,10 @@ int main(int argc, char** argv) { process_timer.Stop(); process_timer.Start(1000, true); - uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); - zone->StartShutdownTimer(shutdown_timer); + if (zone && zone->GetZoneID() && zone->GetInstanceVersion()) { + uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion()); + zone->StartShutdownTimer(shutdown_timer); + } } else if (!previous_loaded && current_loaded) { process_timer.Stop(); diff --git a/zone/npc.cpp b/zone/npc.cpp index a4e6c4214..c13841358 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -436,6 +436,26 @@ void NPC::SetTarget(Mob* mob) { //attack_timer.Disable(); attack_dw_timer.Disable(); } + + // either normal pet and owner is client or charmed pet and owner is client + Mob *owner = nullptr; + if (IsPet() && IsPetOwnerClient()) { + owner = GetOwner(); + } else if (IsCharmed()) { + owner = GetOwner(); + if (owner && !owner->IsClient()) + owner = nullptr; + } + + if (owner) { + auto client = owner->CastToClient(); + if (client->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { + auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); + auto ct = (ClientTarget_Struct *)app->pBuffer; + ct->new_target = mob ? mob->GetID() : 0; + client->FastQueuePacket(&app); + } + } Mob::SetTarget(mob); } diff --git a/zone/pets.cpp b/zone/pets.cpp index 0c150f743..ef895410b 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -475,22 +475,6 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po // Class should use npc constructor to set light properties } -void Pet::SetTarget(Mob *mob) -{ - if (mob == GetTarget()) - return; - - auto owner = GetOwner(); - if (owner && owner->IsClient() && owner->CastToClient()->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { - auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); - auto ct = (ClientTarget_Struct *)app->pBuffer; - ct->new_target = mob ? mob->GetID() : 0; - owner->CastToClient()->QueuePacket(app); - safe_delete(app); - } - NPC::SetTarget(mob); -} - bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) { return GetPoweredPetEntry(pet_type, 0, into); } diff --git a/zone/pets.h b/zone/pets.h index edb6dbe95..1b9811149 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -7,7 +7,6 @@ struct NPCType; class Pet : public NPC { public: Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); - virtual void SetTarget(Mob *mob); virtual bool CheckSpellLevelRestriction(uint16 spell_id); }; diff --git a/zone/raids.cpp b/zone/raids.cpp index 9b33056c0..2bbd83064 100644 --- a/zone/raids.cpp +++ b/zone/raids.cpp @@ -323,6 +323,10 @@ void Raid::SaveRaidLeaderAA() void Raid::UpdateGroupAAs(uint32 gid) { + + if (gid < 0 || gid > MAX_RAID_GROUPS) + return; + Client *gl = GetGroupLeader(gid); if (gl) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 57ea1c781..03b144b5a 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -37,6 +37,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) case EQEmu::skills::SkillDragonPunch: case EQEmu::skills::SkillEagleStrike: case EQEmu::skills::SkillTigerClaw: + case EQEmu::skills::SkillRoundKick: if (skill_level >= 25) base++; if (skill_level >= 75) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 375572014..9f78cb35d 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -137,6 +137,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove buffs[buffslot].magic_rune = 0; buffs[buffslot].numhits = 0; + if (spells[spell_id].numhits > 0) { + + int numhit = spells[spell_id].numhits; + + numhit += numhit * caster->GetFocusEffect(focusFcLimitUse, spell_id) / 100; + numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id); + buffs[buffslot].numhits = numhit; + } + if (spells[spell_id].EndurUpkeep > 0) SetEndurUpkeep(true); @@ -184,14 +193,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } } - if(spells[spell_id].numhits > 0 && buffslot >= 0){ - - int numhit = spells[spell_id].numhits; - - numhit += numhit*caster->GetFocusEffect(focusFcLimitUse, spell_id)/100; - numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id); - buffs[buffslot].numhits = numhit; - } if (!IsPowerDistModSpell(spell_id)) SetSpellPowerDistanceMod(0); diff --git a/zone/spells.cpp b/zone/spells.cpp index 877886757..c5a5a0b48 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3309,8 +3309,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid buffs[emptyslot].spellid = spell_id; buffs[emptyslot].casterlevel = caster_level; - if (caster && caster->IsClient()) - strcpy(buffs[emptyslot].caster_name, caster->GetName()); + if (caster && !caster->IsAura()) // maybe some other things we don't want to ... + strcpy(buffs[emptyslot].caster_name, caster->GetCleanName()); else memset(buffs[emptyslot].caster_name, 0, 64); buffs[emptyslot].casterid = caster ? caster->GetID() : 0; @@ -5548,6 +5548,8 @@ void Client::SendBuffNumHitPacket(Buffs_Struct &buff, int slot) bi->entries[0].spell_id = buff.spellid; bi->entries[0].tics_remaining = buff.ticsremaining; bi->entries[0].num_hits = buff.numhits; + strn0cpy(bi->entries[0].caster, buff.caster_name, 64); + bi->name_lengths = strlen(bi->entries[0].caster); FastQueuePacket(&outapp); } @@ -5633,6 +5635,7 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) else buff->type = 0; + buff->name_lengths = 0; // hacky shit uint32 index = 0; for(int i = 0; i < buff_count; ++i) { @@ -5642,6 +5645,8 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) buff->entries[index].spell_id = buffs[i].spellid; buff->entries[index].tics_remaining = buffs[i].ticsremaining; buff->entries[index].num_hits = buffs[i].numhits; + strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64); + buff->name_lengths += strlen(buff->entries[index].caster); ++index; } } 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 87000f940..d5bcfbeb8 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,20 @@ 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; + undetectable = false; } Trap::~Trap() @@ -80,8 +90,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 +98,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 +112,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 +148,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 +221,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 &trap_curdist, bool detected) +{ float dist = 999999; Trap* current_trap = nullptr; @@ -215,61 +264,161 @@ 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; } -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, undetectable 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 +431,13 @@ 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 = 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); } return true; @@ -318,3 +472,87 @@ 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, 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, undetectable 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 = atobool(row[16]); + trap->undetectable = atobool(row[17]); + 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..e20e2314f 100644 --- a/zone/trap.h +++ b/zone/trap.h @@ -49,11 +49,14 @@ public: NPC * GetHiddenTrigger() { return hiddenTrigger; } void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; } void CreateHiddenTrigger(); - + void DestroyHiddenTrigger() { hiddenTrigger = nullptr; } + 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 +70,12 @@ 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. + bool undetectable; 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.cpp b/zone/zonedb.cpp index bd28bcba3..e23d534c5 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1433,6 +1433,11 @@ bool ZoneDatabase::SaveCharacterInventorySnapshot(uint32 character_id){ } bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){ + + /* If this is ever zero - the client hasn't fully loaded and potentially crashed during zone */ + if (account_id <= 0) + return false; + clock_t t = std::clock(); /* Function timer start */ std::string query = StringFormat( "REPLACE INTO `character_data` (" @@ -2953,9 +2958,11 @@ uint32 ZoneDatabase::GetKarma(uint32 acct_id) if (!results.Success()) return 0; - auto row = results.begin(); + for (auto row = results.begin(); row != results.end(); ++row) { + return atoi(row[0]); + } - return atoi(row[0]); + return 0; } void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount) 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);