Resolve Deadlock in MSpawnList against CombatProcess, barebones of disarm trap

Resolved deadlock in CombatProcess (dead_spawns).  Fixes #31.

Added base for issue #24
This commit is contained in:
Image 2020-03-08 20:42:54 -04:00
parent 1149dc7f2a
commit bd2658946f
12 changed files with 207 additions and 89 deletions

View file

@ -1075,7 +1075,7 @@ void Entity::KillSpawn(Spawn* dead, int8 damage_type, int16 kill_blow_type) {
// Kill movement for the dead npc so the corpse doesn't move
dead->CalculateRunningLocation(true);
GetZone()->KillSpawn(dead, this, true, damage_type, kill_blow_type);
GetZone()->KillSpawn(true, dead, this, true, damage_type, kill_blow_type);
}
void Entity::ProcessCombat() {

View file

@ -1758,7 +1758,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
if(dead && dead->IsPlayer() == false){
dead->SetHP(0);
if(sep && sep->arg[0] && sep->IsNumber(0) && atoi(sep->arg[0]) == 1)
client->GetCurrentZone()->RemoveSpawn(dead, true);
client->GetCurrentZone()->RemoveSpawn(false, dead, true);
else
client->GetPlayer()->KillSpawn(dead);
}else{
@ -3327,7 +3327,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
delete_spawn = true;
}
if(delete_spawn)
client->GetCurrentZone()->RemoveSpawn(spawn, true, false);
client->GetCurrentZone()->RemoveSpawn(false, spawn, true, false);
else
spawn->SetSpawnLocationID(0);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Successfully removed spawn from zone");

View file

@ -48,6 +48,7 @@ Entity::Entity(){
memset(&ranged_combat_data, 0, sizeof(CombatData));
memset(&info_struct, 0, sizeof(InfoStruct));
loot_coins = 0;
trap_triggered = false;
memset(&features, 0, sizeof(CharFeatures));
memset(&equipment, 0, sizeof(EQ2_Equipment));
pet = 0;
@ -1196,7 +1197,7 @@ void Entity::DismissPet(NPC* pet, bool from_death) {
// remove the spawn from the world
if (!from_death && pet->GetPetType() != PET_TYPE_CHARMED)
GetZone()->RemoveSpawn(pet);
GetZone()->RemoveSpawn(false, pet);
}
float Entity::CalculateBonusMod() {

View file

@ -502,6 +502,12 @@ public:
void AddLootCoins(int32 coins){
loot_coins += coins;
}
bool HasTrapTriggered() {
return trap_triggered;
}
void SetTrapTriggered(bool triggered) {
trap_triggered = triggered;
}
void AddLootItem(int32 id, int16 charges = 1){
Item* master_item = master_item_list.GetItem(id);
if(master_item){
@ -817,6 +823,7 @@ private:
float max_speed;
vector<Item*> loot_items;
int32 loot_coins;
bool trap_triggered;
int8 deity;
sint16 regen_hp_rate;
sint16 regen_power_rate;

View file

@ -184,7 +184,7 @@ int EQ2Emu_lua_KillSpawn(lua_State* state) {
Spawn* killer = lua_interface->GetSpawn(state, 2);
bool send_packet = (lua_interface->GetInt8Value(state, 3) == 1);
if(dead && dead->Alive() && dead->GetZone())
dead->GetZone()->KillSpawn(dead, killer, send_packet);
dead->GetZone()->KillSpawn(true, dead, killer, send_packet);
return 0;
}
@ -3351,7 +3351,7 @@ int EQ2Emu_lua_Harvest(lua_State* state){
((GroundSpawn*)node)->ProcessHarvest(client);
if(((GroundSpawn*)node)->GetNumberHarvests() == 0)
player->GetZone()->RemoveSpawn(node, true);
player->GetZone()->RemoveSpawn(false, node, true);
}
}
else if(player && player->IsPlayer()){

View file

@ -481,3 +481,33 @@ void PlayerSkillList::RemoveSkillBonus(int32 spell_id) {
safe_delete(sb);
}
}
int Skill::CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty)
{
if (chest_difficulty < 2) // no triggers on this chest type
return 1;
int chest_diff_result = targetLevel * chest_difficulty;
float base_difficulty = 15.0f;
float fail_threshold = 10.0f;
float chance = ((100.0f - base_difficulty) * ((float)current_val / (float)chest_diff_result));
if (chance > (100.0f - base_difficulty))
{
chance = 100.0f - base_difficulty;
}
float d100 = (float)MakeRandomFloat(0, 100);
if (d100 <= chance)
return 1;
else
{
if (d100 > (chance + fail_threshold))
return -1;
}
return 0;
}

View file

@ -72,6 +72,8 @@ public:
EQ2_16BitString name;
EQ2_16BitString description;
bool save_needed;
int CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty=0);
};
class MasterSkillList{

View file

@ -191,7 +191,7 @@ int32 WorldDatabase::LoadCharacterSpells(int32 char_id, Player* player)
void WorldDatabase::SavePlayerSpells(Client* client)
{
if(!client)
if(!client || client->GetCharacterID() < 1)
return;
LogWrite(SPELL__DEBUG, 3, "Spells", "Saving Spell(s) for Player: '%s'", client->GetPlayer()->GetName());
@ -3090,7 +3090,7 @@ bool WorldDatabase::SaveCombinedSpawnLocation(ZoneServer* zone, Spawn* in_spawn,
}
for(itr=spawns->begin();itr!=spawns->end();itr++){
spawn = *itr;
zone->RemoveSpawn(spawn);
zone->RemoveSpawn(false, spawn);
}
safe_delete(spawns);
}

View file

@ -586,7 +586,7 @@ void Client::HandlePlayerRevive(int32 point_id)
safe_delete(packet);
}
GetCurrentZone()->RemoveSpawn(player, false);
GetCurrentZone()->RemoveSpawn(false, player, false);
m_resurrect.writelock(__FUNCTION__, __LINE__);
if(current_rez.active)
@ -1299,7 +1299,7 @@ bool Client::HandlePacket(EQApplicationPacket *app) {
GetPlayer()->SetCharSheetChanged(true);
GetCurrentZone()->SendDamagePacket(0, GetPlayer(), DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, GetPlayer()->GetInvulnerable() ? DAMAGE_PACKET_RESULT_INVULNERABLE : DAMAGE_PACKET_RESULT_SUCCESSFUL, DAMAGE_PACKET_DAMAGE_TYPE_FALLING, damage, 0);
if(GetPlayer()->GetHP() == 0) {
GetCurrentZone()->KillSpawn(GetPlayer(), 0);
GetCurrentZone()->KillSpawn(false, GetPlayer(), 0);
}
}
}
@ -2724,7 +2724,7 @@ void ClientList::Remove(Client* client, bool remove_data) {
void Client::SetCurrentZone(int32 id){
if(current_zone){
//current_zone->GetCombat()->RemoveHate(player);
current_zone->RemoveSpawn(player, false);
current_zone->RemoveSpawn(false, player, false);
}
SetCurrentZone(zone_list.Get(id));
@ -2733,7 +2733,7 @@ void Client::SetCurrentZone(int32 id){
void Client::SetCurrentZoneByInstanceID(int32 id,int32 zoneid){
if(current_zone){
//current_zone->GetCombat()->RemoveHate(player);
current_zone->RemoveSpawn(player, false);
current_zone->RemoveSpawn(false, player, false);
}
SetCurrentZone(zone_list.GetByInstanceID(id,zoneid));
@ -3181,7 +3181,7 @@ void Client::Zone(ZoneServer* new_zone, bool set_coords){
LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Removing player from current zone...", __FUNCTION__);
GetCurrentZone()->RemoveSpawn(player, false);
GetCurrentZone()->RemoveSpawn(false, player, false);
LogWrite(CCLIENT__DEBUG, 0, "Client", "%s: Setting zone to '%s'...", __FUNCTION__, new_zone->GetZoneName());
SetCurrentZone(new_zone);
@ -3952,7 +3952,7 @@ void Client::Loot(int32 total_coins, vector<Item*>* items, Entity* entity){
memcpy(data, &packet_size, sizeof(int32));
packet_size += sizeof(int32);
EQ2Packet* outapp = new EQ2Packet(OP_ClientCmdMsg, data, packet_size);
//DumpPacket(outapp);
//DumpPacket(outapp);
QueuePacket(outapp);
safe_delete_array(data);
safe_delete(packet);
@ -3967,36 +3967,87 @@ void Client::Loot(Entity* entity){
Loot(total_coins, entity->GetLootItems(), entity);
entity->UnlockLoot();
int32 state = 0;
// Check for the chest and set the action state
/*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/
if (entity->GetModelType() == 4034) {
// small chest, open with copper coins
state = 11899;
}
else if (entity->GetModelType() == 5864) {
// treasure chest, open with silver coins
state = 11901;
}
else if (entity->GetModelType() == 5865) {
// ornate chest, open with gold coins
state = 11900;
}
else if (entity->GetModelType() == 4015) {
// exquisite chest, open with gold coins and jewels as well as a glow effect
state = 11898;
}
// We set the visual state with out updating so those not in range will see it opened when it is finally sent to them,
// for those in range the SendStateCommand will cause it to animate open.
entity->SetVisualState(state, false);
GetCurrentZone()->SendStateCommand(entity, state);
OpenChest(entity);
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not allowed to loot at this time.");
}
void Client::OpenChest(Entity* entity)
{
if (!entity)
return;
int8 chest_difficulty = 0;
int32 state = 0;
// Check for the chest and set the action state
/*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/
if (entity->GetModelType() == 4034) {
// small chest, open with copper coins
// does not include traps, however can be disarmed
chest_difficulty = 1;
state = 11899;
}
else if (entity->GetModelType() == 5864) {
// treasure chest, open with silver coins
chest_difficulty = 2;
state = 11901;
}
else if (entity->GetModelType() == 5865) {
// ornate chest, open with gold coins
chest_difficulty = 3;
state = 11900;
}
else if (entity->GetModelType() == 4015) {
// exquisite chest, open with gold coins and jewels as well as a glow effect
chest_difficulty = 5;
state = 11898;
}
boolean firstChestOpen = false;
if (chest_difficulty > 0 && !entity->HasTrapTriggered())
{
Skill* disarmSkill = GetPlayer()->GetSkillByName("Disarm Trap", false);
firstChestOpen = true;
entity->SetTrapTriggered(true);
if (disarmSkill)
{
if (disarmSkill->CheckDisarmSkill(entity->GetLevel(), chest_difficulty) < 1)
{
//Spell* spell = master_spell_list.GetSpell(spellid, tier);
//GetPlayer()->GetZone()->GetSpellProcess()->CastInstant(spell, (Entity*)GetPlayer(), (Entity*)GetPlayer());
SimpleMessage(CHANNEL_COLOR_YELLOW, "You failed to disarm the chest.");
}
else
{
SimpleMessage(CHANNEL_COLOR_YELLOW, "You have disarmed the chest.");
GetPlayer()->GetSkillByName("Disarm Trap", true);
}
}
else
{
SimpleMessage(CHANNEL_COLOR_YELLOW, "You triggered the chest.");
}
}
else if(!entity->HasTrapTriggered())
{
firstChestOpen = true;
entity->SetTrapTriggered(true);
}
// We set the visual state with out updating so those not in range will see it opened when it is finally sent to them,
// for those in range the SendStateCommand will cause it to animate open.
// TODO: when player enters radius that does not have visual state, update visual state
if (firstChestOpen)
entity->SetVisualState(state, false);
GetCurrentZone()->SendStateCommand(entity, state);
}
Spawn* Client::GetBanker(){
return banker;
}
@ -5006,7 +5057,7 @@ void Client::SaveCombineSpawns(const char* name){
SimpleMessage(CHANNEL_COLOR_YELLOW, "Error: You only have a single Spawn in the group!");
else if(database.SaveCombinedSpawnLocation(GetCurrentZone(), combine_spawn, name)){
Message(CHANNEL_COLOR_YELLOW, "Successfully combined %u spawns into spawn location: %u", count, combine_spawn->GetSpawnLocationID());
GetCurrentZone()->RemoveSpawn(combine_spawn);
GetCurrentZone()->RemoveSpawn(false, combine_spawn);
}
else
SimpleMessage(CHANNEL_COLOR_YELLOW, "Error saving spawn group, check console for details.");

View file

@ -244,6 +244,7 @@ public:
void SendPendingLoot(int32 total_coins, Entity* entity);
void Loot(int32 total_coins, vector<Item*>* items, Entity* entity);
void Loot(Entity* entity);
void OpenChest(Entity* entity);
void CheckPlayerQuestsKillUpdate(Spawn* spawn);
void CheckPlayerQuestsChatUpdate(Spawn* spawn);
void CheckPlayerQuestsItemUpdate(Item* item);

View file

@ -1202,9 +1202,9 @@ void ZoneServer::DelayedSpawnRemoval(bool force_delete_all) {
}
if (spawn->IsPlayer())
RemoveSpawn(spawn, false);
RemoveSpawn(false, spawn, false);
else
RemoveSpawn(spawn);
RemoveSpawn(false, spawn);
}
}
}
@ -1614,7 +1614,7 @@ void ZoneServer::CheckDeadSpawnRemoval() {
if (!spawn->IsPlayer())
{
dead_spawns.erase(spawn->GetID());
RemoveSpawn(spawn, true, true, false);
RemoveSpawn(true, spawn, true, true, false);
}
}
}
@ -1831,7 +1831,7 @@ void ZoneServer::ProcessDrowning(){
vector<Client*>::iterator itr;
for(itr = dead_list.begin(); itr != dead_list.end(); itr++){
RemoveDrowningVictim((*itr)->GetPlayer());
KillSpawn((*itr)->GetPlayer(), 0);
KillSpawn(false, (*itr)->GetPlayer(), 0);
(*itr)->SimpleMessage(CHANNEL_COLOR_WHITE, "You are sleeping with the fishes! Glug, glug...");
}
}
@ -2863,7 +2863,7 @@ void ZoneServer::RemoveClient(Client* client)
client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetDeityPet());
client->GetPlayer()->DismissPet((NPC*)client->GetPlayer()->GetCosmeticPet());
RemoveSpawn(client->GetPlayer(), false);
RemoveSpawn(false, client->GetPlayer(), false);
connected_clients.Remove(client, true, DisconnectClientTimer);
//}
}
@ -3644,7 +3644,7 @@ void ZoneServer::KillSpawnByDistance(Spawn* spawn, float max_distance, bool incl
test_spawn = itr->second;
if(test_spawn && test_spawn->IsEntity() && test_spawn != spawn && (!test_spawn->IsPlayer() || include_players)){
if(test_spawn->GetDistance(spawn) < max_distance)
KillSpawn(test_spawn, spawn, send_packet);
KillSpawn(true, test_spawn, spawn, send_packet);
}
}
MSpawnList.releasereadlock(__FUNCTION__, __LINE__);
@ -3681,7 +3681,7 @@ void ZoneServer::RemoveFromRangeMap(Client* client){
}
*/
void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool lock)
void ZoneServer::RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spawn, bool respawn, bool lock)
{
LogWrite(ZONE__DEBUG, 3, "Zone", "Processing RemoveSpawn function...");
@ -3690,18 +3690,74 @@ void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool
}
RemoveSpawnSupportFunctions(spawn);
RemoveDeadEnemyList(spawn);
if (reloading)
RemoveDeadEnemyList(spawn);
LogWrite(ZONE__DEBUG, 7, "Zone", "Lock DeadSpawns...");
if (lock)
MDeadSpawns.writelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 7, "Zone", "Erase DeadSpawns...");
if (dead_spawns.count(spawn->GetID()) > 0)
dead_spawns.erase(spawn->GetID());
if (lock)
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);;
MDeadSpawns.releasewritelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 7, "Zone", "End DeadSpawns...");
LogWrite(ZONE__DEBUG, 7, "Zone", "SpawnExpireTimers...");
if (spawn_expire_timers.count(spawn->GetID()) > 0)
spawn_expire_timers.erase(spawn->GetID());
LogWrite(ZONE__DEBUG, 7, "Zone", "SpawnExpireTimers Done...");
RemoveDelayedSpawnRemove(spawn);
LogWrite(ZONE__DEBUG, 7, "Zone", "RemoveDelayedSpawnRemove Done...");
// Clear the pointer in the spawn list, spawn thread will remove the key
if (!spawnListLocked)
MSpawnList.writelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 7, "Zone", "RemoveSpawnList Start...");
spawn_list[spawn->GetID()] = 0;
if (!spawnListLocked)
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 7, "Zone", "RemoveSpawnList Done...");
PacketStruct* packet = 0;
int16 packet_version = 0;
Client* client = 0;
vector<Client*>::iterator client_itr;
LogWrite(ZONE__DEBUG, 7, "Zone", "ClientList Start...");
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if (client) {
if (client->IsConnected() && (!packet || packet_version != client->GetVersion()))
{
safe_delete(packet);
packet_version = client->GetVersion();
packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version);
}
if (client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn)
client->GetPlayer()->SetTarget(0);
SendRemoveSpawn(client, spawn, packet, delete_spawn);
if (spawn_range_map.count(client) > 0)
spawn_range_map.Get(client)->erase(spawn->GetID());
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 7, "Zone", "ClientList End...");
safe_delete(packet);
if(respawn && !spawn->IsPlayer() && spawn->GetRespawnTime() > 0 && spawn->GetSpawnLocationID() > 0)
{
@ -3725,50 +3781,20 @@ void ZoneServer::RemoveSpawn(Spawn* spawn, bool delete_spawn, bool respawn, bool
}
}
else
respawn_timers.Put(spawn->GetSpawnLocationID(), Timer::GetCurrentTime2() + spawn->GetRespawnTime()*1000);
}
PacketStruct* packet = 0;
int16 packet_version = 0;
Client* client = 0;
// Clear the pointer in the spawn list, spawn thread will remove the key
MSpawnList.writelock(__FUNCTION__, __LINE__);
spawn_list.erase(spawn->GetID());
MSpawnList.releasewritelock(__FUNCTION__, __LINE__);
vector<Client*>::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if (client) {
if(client->IsConnected() && (!packet || packet_version != client->GetVersion()))
{
safe_delete(packet);
packet_version = client->GetVersion();
packet = configReader.getStruct("WS_DestroyGhostCmd", packet_version);
}
if(client->GetPlayer()->HasTarget() && client->GetPlayer()->GetTarget() == spawn)
client->GetPlayer()->SetTarget(0);
SendRemoveSpawn(client, spawn, packet, delete_spawn);
if(spawn_range_map.count(client) > 0)
spawn_range_map.Get(client)->erase(spawn->GetID());
{
int32 spawnLocationID = spawn->GetSpawnLocationID();
int32 spawnRespawnTime = spawn->GetRespawnTime();
safe_delete(spawn);
respawn_timers.Put(spawnLocationID, Timer::GetCurrentTime2() + spawnRespawnTime * 1000);
}
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
safe_delete(packet);
// Do we really need the mutex locks and check to dead_spawns as we remove it from dead spawns at the start of this function
if (lock)
if (lock && !respawn)
MDeadSpawns.readlock(__FUNCTION__, __LINE__);
if(delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
if(!respawn && delete_spawn && dead_spawns.count(spawn->GetID()) == 0)
AddPendingDelete(spawn);
if (lock)
if (lock && !respawn)
MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__);
LogWrite(ZONE__DEBUG, 3, "Zone", "Done processing RemoveSpawn function...");
@ -4017,7 +4043,7 @@ void ZoneServer::Despawn(Spawn* spawn, int32 timer){
AddDeadSpawn(spawn, timer);
}
void ZoneServer::KillSpawn(Spawn* dead, Spawn* killer, bool send_packet, int8 damage_type, int16 kill_blow_type)
void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 damage_type, int16 kill_blow_type)
{
MDeadSpawns.readlock(__FUNCTION__, __LINE__);
if(!dead || this->dead_spawns.count(dead->GetID()) > 0) {

View file

@ -267,7 +267,7 @@ public:
void AddSpawnGroupChance(int32 group_id, float percent);
void RemoveSpawn(Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true);
void RemoveSpawn(bool spawnListLocked, Spawn* spawn, bool delete_spawn = true, bool respawn = true, bool lock = true);
void ProcessSpawnLocations();
void SendQuestUpdates(Client* client, Spawn* spawn = 0);
@ -295,7 +295,7 @@ public:
vector<Entity*> GetPlayers();
void KillSpawn(Spawn* dead, Spawn* killer, bool send_packet = true, int8 damage_type = 0, int16 kill_blow_type = 0);
void KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet = true, int8 damage_type = 0, int16 kill_blow_type = 0);
void SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name);
void SendHealPacket(Spawn* caster, Spawn* target, int16 type, int32 heal_amt, const char* spell_name);