- Fix #528 - IMMUNITY_TYPE_STRIKETHROUGH type 10 for AddImmunitySpell and like functions

- Fix #501 - /assist added, /assist on, /assist off, /assist [name], /assist *target*
update commands set handler=535 where command='assist';
- Fix #511 - loot tables not properly honoring max drops
- Fixed a few crashes / deadlocks with movement
- Start of mastery work for #503 -- primary/secondary damage now supports mastery skill
This commit is contained in:
Emagi 2024-04-05 18:52:18 -04:00
parent 4383e6b20a
commit c1e8768769
15 changed files with 257 additions and 71 deletions

View file

@ -4110,55 +4110,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
case COMMAND_ATTACK:
case COMMAND_AUTO_ATTACK:{
int8 type = 1;
bool update = false;
Player* player = client->GetPlayer();
if(!player)
break;
bool incombat = player->EngagedInCombat();
if(sep && sep->arg[0] && sep->IsNumber(0))
type = atoi(sep->arg[0]);
if(!client->GetPlayer()->Alive()){
client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now.");
break;
}
if(type == 0){
if(incombat)
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
player->StopCombat(type);
update = true;
}
else {
if(type == 2){
player->InCombat(false);
if(incombat && player->GetRangeAttack()){
player->StopCombat(type);
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
update = true;
}
else{
player->SetRangeAttack(true);
player->InCombat(true, true);
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
update = true;
}
}
else {
player->InCombat(false, true);
player->SetRangeAttack(false);
player->InCombat(true);
if(!incombat) {
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
update = true;
}
}
/*else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/
}
if(update) {
player->SetCharSheetChanged(true);
}
Command_AutoAttack(client, sep);
break;
}
case COMMAND_DEPOP:{
@ -5724,6 +5676,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
case COMMAND_SHARE_QUEST: { Command_ShareQuest(client, sep); break; }
case COMMAND_YELL: { Command_Yell(client, sep); break; }
case COMMAND_SETAUTOLOOTMODE: { Command_SetAutoLootMode(client, sep); break; }
case COMMAND_ASSIST: { Command_Assist(client, sep); break; }
default:
{
LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@ -10970,6 +10923,8 @@ void Commands::Command_WeaponStats(Client* client)
if (primary) {
client->Message(0, "Name: %s", primary->name.c_str());
client->Message(0, "Base Damage: %u - %u", primary->weapon_info->damage_low3, primary->weapon_info->damage_high3);
client->Message(0, "Mastery Damage: %u - %u", primary->weapon_info->damage_low2, primary->weapon_info->damage_high2);
client->Message(0, "Damage: %u - %u", primary->weapon_info->damage_low1, primary->weapon_info->damage_high1);
client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetPrimaryWeaponMinDamage() : player->GetPrimaryWeaponMinDamage(),
target ? ((Entity*)target)->GetPrimaryWeaponMaxDamage() : player->GetPrimaryWeaponMaxDamage());
client->Message(0, "Actual Delay: %u", target ? ((Entity*)target)->GetPrimaryWeaponDelay() : player->GetPrimaryWeaponDelay());
@ -10992,6 +10947,8 @@ void Commands::Command_WeaponStats(Client* client)
client->SimpleMessage(0, "Secondary:");
client->Message(0, "Name: %s", secondary->name.c_str());
client->Message(0, "Base Damage: %u - %u", secondary->weapon_info->damage_low3, secondary->weapon_info->damage_high3);
client->Message(0, "Mastery Damage: %u - %u", secondary->weapon_info->damage_low2, secondary->weapon_info->damage_high2);
client->Message(0, "Damage: %u - %u", secondary->weapon_info->damage_low1, secondary->weapon_info->damage_high1);
client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetSecondaryWeaponMinDamage() : player->GetSecondaryWeaponMinDamage(),
target ? ((Entity*)target)->GetSecondaryWeaponMaxDamage() : player->GetSecondaryWeaponMaxDamage());
client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetSecondaryWeaponDelay() : player->GetSecondaryWeaponDelay() * 0.1);
@ -11004,6 +10961,8 @@ void Commands::Command_WeaponStats(Client* client)
if (ranged) {
client->Message(0, "Name: %s", ranged->name.c_str());
client->Message(0, "Base Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low3, ranged->ranged_info->weapon_info.damage_high3);
client->Message(0, "Mastery Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low2, ranged->ranged_info->weapon_info.damage_high2);
client->Message(0, "Damage: %u - %u", ranged->ranged_info->weapon_info.damage_low1, ranged->ranged_info->weapon_info.damage_high1);
client->Message(0, "Actual Damage: %u - %u", target ? ((Entity*)target)->GetRangedWeaponMinDamage() : player->GetRangedWeaponMinDamage(),
target ? ((Entity*)target)->GetRangedWeaponMaxDamage() : player->GetRangedWeaponMaxDamage());
client->Message(0, "Actual Delay: %d", target ? ((Entity*)target)->GetRangeWeaponDelay() : player->GetRangeWeaponDelay() * 0.1);
@ -12156,3 +12115,101 @@ void Commands::Command_SetAutoLootMode(Client* client, Seperator* sep) {
client->SendDefaultGroupOptions();
}
}
/*
Function: Command_AutoAttack()
Purpose : Attack / Auto Attack
Example : /attack, /autoattack type
*/
void Commands::Command_AutoAttack(Client* client, Seperator* sep) {
int8 type = 1;
bool update = false;
Player* player = client->GetPlayer();
if(!player)
return;
bool incombat = player->EngagedInCombat();
if(sep && sep->arg[0] && sep->IsNumber(0))
type = atoi(sep->arg[0]);
if(!client->GetPlayer()->Alive()){
client->SimpleMessage(CHANNEL_COLOR_RED,"You cannot do that right now.");
return;
}
if(type == 0){
if(incombat)
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
player->StopCombat(type);
update = true;
}
else {
if(type == 2){
player->InCombat(false);
if(incombat && player->GetRangeAttack()){
player->StopCombat(type);
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You stop fighting.");
update = true;
}
else{
player->SetRangeAttack(true);
player->InCombat(true, true);
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
update = true;
}
}
else {
player->InCombat(false, true);
player->SetRangeAttack(false);
player->InCombat(true);
if(!incombat) {
client->SimpleMessage(CHANNEL_GENERAL_COMBAT, "You start fighting.");
update = true;
}
}
/*else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You cannot attack that!");*/
}
if(update) {
player->SetCharSheetChanged(true);
}
}
/*
Function: Command_Assist()
Purpose : Assist target
Example : /assist [name]
* Uses target or character name
*/
void Commands::Command_Assist(Client* client, Seperator* sep) {
Entity* player = (Entity*)client->GetPlayer();
Spawn* res = nullptr;
if(sep && sep->arg[0]) {
if(!stricmp(sep->arg[0], "on")) {
database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "1");
return;
}
else if(!stricmp(sep->arg[0], "off")) {
database.insertCharacterProperty(client, CHAR_PROPERTY_ASSISTAUTOATTACK, "0");
return;
}
Client* otherClient = client->GetPlayer()->GetZone()->GetClientByName(sep->arg[0]);
if(otherClient) {
res = otherClient->GetPlayer();
}
}
if (player->GetTarget()) {
res = player->GetTarget(); // selected target other than self only dis-engages that encounter
}
if(res && res->GetTarget()) {
res = res->GetTarget();
}
if(res) {
client->TargetSpawn(res);
if(client->GetPlayer()->GetInfoStruct()->get_assist_auto_attack() && !player->EngagedInCombat()) {
Command_AutoAttack(client, nullptr);
}
}
}

View file

@ -450,6 +450,8 @@ public:
void Command_ShareQuest(Client* client, Seperator* sep);
void Command_Yell(Client* client, Seperator* sep);
void Command_SetAutoLootMode(Client* client, Seperator* sep);
void Command_AutoAttack(Client* client, Seperator* sep);
void Command_Assist(Client* client, Seperator* sep);
// AA Commands
void Get_AA_Xml(Client* client, Seperator* sep);
@ -939,6 +941,7 @@ private:
#define COMMAND_SHARE_QUEST 533
#define COMMAND_SETAUTOLOOTMODE 534
#define COMMAND_ASSIST 535
#define GET_AA_XML 750
#define ADD_AA 751

View file

@ -363,6 +363,7 @@ void Entity::MapInfoStruct()
get_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::get_group_lock_method, &info_struct);
get_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::get_group_solo_autolock, &info_struct);
get_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::get_group_auto_loot_method, &info_struct);
get_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::get_assist_auto_attack, &info_struct);
get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct);
get_string_funcs["combat_action_state"] = l::bind(&InfoStruct::get_combat_action_state, &info_struct);
@ -564,6 +565,7 @@ void Entity::MapInfoStruct()
set_int8_funcs["group_lock_method"] = l::bind(&InfoStruct::set_group_lock_method, &info_struct, l::_1);
set_int8_funcs["group_solo_autolock"] = l::bind(&InfoStruct::set_group_solo_autolock, &info_struct, l::_1);
set_int8_funcs["group_auto_loot_method"] = l::bind(&InfoStruct::set_group_auto_loot_method, &info_struct, l::_1);
set_int8_funcs["assist_auto_attack"] = l::bind(&InfoStruct::set_assist_auto_attack, &info_struct, l::_1);
set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1);
set_string_funcs["combat_action_state"] = l::bind(&InfoStruct::set_combat_action_state, &info_struct, l::_1);
@ -762,6 +764,62 @@ void Entity::SetSecondaryLastAttackTime(int32 new_time){
GetInfoStruct()->set_secondary_last_attack_time(new_time);
}
void Entity::GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage) {
if(!low_damage || !high_damage)
return;
int32 selected_low_dmg = item->weapon_info->damage_low3;
int32 selected_high_dmg = item->weapon_info->damage_high3;
if(IsPlayer()) {
float skillMultiplier = rule_manager.GetGlobalRule(R_Player, LevelMasterySkillMultiplier)->GetFloat();
if(skillMultiplier <= 0.0f) {
skillMultiplier = 1.0f;
}
int32 min_level_skill = (int32)((float)item->generic_info.adventure_default_level*skillMultiplier);
int32 rec_level_skill = (int32)((float)item->details.recommended_level*skillMultiplier);
if(min_level_skill > rec_level_skill) {
rec_level_skill = rec_level_skill;
}
Skill* masterySkill = ((Player*)this)->skill_list.GetSkill(item->generic_info.skill_req2);
if(masterySkill) {
LogWrite(PLAYER__ERROR, 0, "Player", "Item has skill %s %u requirement", masterySkill->name.data.c_str(), item->generic_info.skill_req2);
int16 skillID = master_item_list.GetItemStatIDByName(masterySkill->name.data);
int32 skill_chance = (int32)CalculateSkillWithBonus((char*)masterySkill->name.data.c_str(), master_item_list.GetItemStatIDByName(masterySkill->name.data), false);
if(skill_chance >= min_level_skill && skill_chance < rec_level_skill) {
int32 diff_skill = rec_level_skill - skill_chance;
if(diff_skill < 1) {
selected_low_dmg = item->weapon_info->damage_low2;
selected_high_dmg = item->weapon_info->damage_high2;
}
else {
diff_skill += 1;
double logResult = log((double)diff_skill) / skillMultiplier;
if(logResult > 1.0f) {
logResult = .95f;
}
selected_low_dmg = (int32)((double)item->weapon_info->damage_low2 * (1.0 - logResult));
if(selected_low_dmg < item->weapon_info->damage_low3) {
selected_low_dmg = item->weapon_info->damage_low3;
}
selected_high_dmg = (int32)((double)item->weapon_info->damage_high2 * (1.0 - logResult));
if(selected_high_dmg < item->weapon_info->damage_high3) {
selected_high_dmg = item->weapon_info->damage_high3;
}
}
}
else if(skill_chance >= rec_level_skill) {
selected_low_dmg = item->weapon_info->damage_low2;
selected_high_dmg = item->weapon_info->damage_high2;
}
}
}
*low_damage = selected_low_dmg;
*high_damage = selected_high_dmg;
}
void Entity::ChangePrimaryWeapon(){
if(GetInfoStruct()->get_override_primary_weapon()) {
return;
@ -770,9 +828,12 @@ void Entity::ChangePrimaryWeapon(){
int32 str_offset_dmg = GetStrengthDamage();
Item* item = equipment_list.GetItem(EQ2_PRIMARY_SLOT);
if(item && item->details.item_id > 0 && item->IsWeapon()){
int32 selected_low_dmg = item->weapon_info->damage_low3;
int32 selected_high_dmg = item->weapon_info->damage_high3;
GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg);
GetInfoStruct()->set_primary_weapon_delay(item->weapon_info->delay * 100);
GetInfoStruct()->set_primary_weapon_damage_low(item->weapon_info->damage_low3 + str_offset_dmg);
GetInfoStruct()->set_primary_weapon_damage_high(item->weapon_info->damage_high3 + str_offset_dmg);
GetInfoStruct()->set_primary_weapon_damage_low(selected_low_dmg + str_offset_dmg);
GetInfoStruct()->set_primary_weapon_damage_high(selected_high_dmg + str_offset_dmg);
GetInfoStruct()->set_primary_weapon_type(item->GetWeaponType());
GetInfoStruct()->set_wield_type(item->weapon_info->wield_type);
}
@ -803,9 +864,12 @@ void Entity::ChangeSecondaryWeapon(){
Item* item = equipment_list.GetItem(EQ2_SECONDARY_SLOT);
if(item && item->details.item_id > 0 && item->IsWeapon()){
int32 selected_low_dmg = item->weapon_info->damage_low3;
int32 selected_high_dmg = item->weapon_info->damage_high3;
GetWeaponDamage(item, &selected_low_dmg, &selected_high_dmg);
GetInfoStruct()->set_secondary_weapon_delay(item->weapon_info->delay * 100);
GetInfoStruct()->set_secondary_weapon_damage_low(item->weapon_info->damage_low3 + str_offset_dmg);
GetInfoStruct()->set_secondary_weapon_damage_high(item->weapon_info->damage_high3 + str_offset_dmg);
GetInfoStruct()->set_secondary_weapon_damage_low(selected_low_dmg + str_offset_dmg);
GetInfoStruct()->set_secondary_weapon_damage_high(selected_high_dmg + str_offset_dmg);
GetInfoStruct()->set_secondary_weapon_type(item->GetWeaponType());
}
else{
@ -1563,6 +1627,8 @@ void Entity::CalculateBonuses(){
CalculateApplyWeight();
UpdateWeapons();
safe_delete(values);
}

View file

@ -287,6 +287,7 @@ struct InfoStruct{
group_lock_method_ = 0;
group_solo_autolock_ = 0;
group_auto_loot_method_ = 0;
assist_auto_attack_ = 0;
action_state_ = std::string("");
}
@ -697,6 +698,7 @@ struct InfoStruct{
int8 get_group_lock_method() { std::lock_guard<std::mutex> lk(classMutex); return group_lock_method_; }
int8 get_group_solo_autolock() { std::lock_guard<std::mutex> lk(classMutex); return group_solo_autolock_; }
int8 get_group_auto_loot_method() { std::lock_guard<std::mutex> lk(classMutex); return group_auto_loot_method_; }
int8 get_assist_auto_attack() { std::lock_guard<std::mutex> lk(classMutex); return assist_auto_attack_; }
std::string get_action_state() { std::lock_guard<std::mutex> lk(classMutex); return action_state_; }
@ -1003,6 +1005,8 @@ struct InfoStruct{
void set_group_solo_autolock(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_solo_autolock_ = value; }
void set_group_auto_loot_method(int8 value) { std::lock_guard<std::mutex> lk(classMutex); group_auto_loot_method_ = value; }
void set_assist_auto_attack(int8 value) { std::lock_guard<std::mutex> lk(classMutex); assist_auto_attack_ = value; }
void set_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); action_state_ = value; }
void set_combat_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); combat_action_state_ = value; }
@ -1223,6 +1227,8 @@ private:
int8 group_solo_autolock_;
int8 group_auto_loot_method_;
int8 assist_auto_attack_;
std::string action_state_;
std::string combat_action_state_;
@ -1323,6 +1329,7 @@ struct ThreatTransfer {
#define IMMUNITY_TYPE_AOE 7
#define IMMUNITY_TYPE_TAUNT 8
#define IMMUNITY_TYPE_RIPOSTE 9
#define IMMUNITY_TYPE_STRIKETHROUGH 10
//class Spell;
//class ZoneServer;
@ -1454,6 +1461,8 @@ public:
bool IsDualWield();
bool BehindTarget(Spawn* target);
bool FlankingTarget(Spawn* target);
void GetWeaponDamage(Item* item, int32* low_damage, int32* high_damage);
void ChangePrimaryWeapon();
void ChangeSecondaryWeapon();
void ChangeRangedWeapon();

View file

@ -849,6 +849,16 @@ ItemStatsValues* MasterItemList::CalculateItemBonuses(Item* item, Entity* entity
id = stat->stat_type*multiplier + stat->stat_subtype;
}
if(entity->IsPlayer()) {
int32 effective_level = entity->GetInfoStructUInt("effective_level");
if(effective_level && effective_level < entity->GetLevel() && item->details.recommended_level > effective_level)
{
int32 diff = item->details.recommended_level - effective_level;
float tmpValue = (float)value;
value = (sint32)(float)(tmpValue / (1.0f + ((float)diff * rule_manager.GetGlobalRule(R_Player, MentorItemDecayRate)->GetFloat())));
}
}
world.AddBonuses(item, values, id, value, entity);
}
return values;

View file

@ -13335,7 +13335,7 @@ int EQ2Emu_lua_StopMovement(lua_State* state) {
return 0;
Spawn* spawn = lua_interface->GetSpawn(state);
if (spawn) {
spawn->StopMovement();
spawn->ResetMovement();
}
lua_interface->ResetFunctionStack(state);
return 0;

View file

@ -1742,8 +1742,31 @@ bool Player::CanEquipItem(Item* item, int8 slot) {
else
client->Message(CHANNEL_COLOR_RED, "Your class may not equip %s.", item->CreateItemLink(client->GetVersion()).c_str());
}
else
client->SimpleMessage(0, "You lack the skill required to equip this item.");
else {
Skill* firstSkill = master_skill_list.GetSkill(item->generic_info.skill_req1);
Skill* secondSkill = master_skill_list.GetSkill(item->generic_info.skill_req2);
std::string msg("");
if(GetClient()->GetAdminStatus() >= 200) {
if(firstSkill && !skill_list.HasSkill(item->generic_info.skill_req1)) {
msg += "(" + std::string(firstSkill->name.data.c_str());
}
if(secondSkill && !skill_list.HasSkill(item->generic_info.skill_req2)) {
if(msg.length() > 0) {
msg += ", ";
}
else {
msg = "(";
}
msg += std::string(secondSkill->name.data.c_str());
}
if(msg.length() > 0) {
msg += ") ";
}
}
client->Message(0, "You lack the skill %srequired to equip this item.",msg.c_str());
}
}
else
client->Message(0, "Item %s isn't equipable.", item->name.c_str());

View file

@ -226,6 +226,7 @@ void RuleManager::Init()
RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%)
RULE_INIT(R_Player, CoinWeightPerHundred, "100.0"); // coin weight per stone, 100.0 = 100 coins per 1 stone
RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range
/* PVP */
RULE_INIT(R_PVP, AllowPVP, "0");

View file

@ -86,6 +86,7 @@ enum RuleType {
WeightPercentCap,
CoinWeightPerHundred,
WeightInflictsSpeed,
LevelMasterySkillMultiplier,
/* PVP */
AllowPVP,

View file

@ -147,6 +147,7 @@ Spawn::Spawn(){
looter_spawn_id = 0;
is_loot_complete = false;
is_loot_dispensed = false;
reset_movement = false;
}
Spawn::~Spawn(){
@ -2847,6 +2848,10 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
}*/
return;
}
if(reset_movement) {
ResetMovement();
reset_movement = false;
}
if (forceMapCheck && GetZone() != nullptr && GetMap() != nullptr && GetMap()->IsMapLoaded())
{
@ -3113,9 +3118,9 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
}*/
}
void Spawn::ResetMovement(bool inFlight){
if(!inFlight)
void Spawn::ResetMovement(){
MMovementLoop.writelock();
vector<MovementData*>::iterator itr;
for(itr = movement_loop.begin(); itr != movement_loop.end(); itr++){
safe_delete(*itr);
@ -3125,10 +3130,9 @@ void Spawn::ResetMovement(bool inFlight){
resume_movement = true;
ClearRunningLocations();
if(!inFlight)
MMovementLoop.releasewritelock();
ValidateRunning(true, (inFlight == false));
ValidateRunning(true, true);
}
void Spawn::AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading){
@ -4541,7 +4545,7 @@ float Spawn::SpawnAngle(Spawn* target, float selfx, float selfz)
void Spawn::StopMovement()
{
ResetMovement(true);
reset_movement = true;
}
bool Spawn::PauseMovement(int32 period_of_time_ms)

View file

@ -1104,7 +1104,7 @@ public:
void MoveToLocation(Spawn* spawn, float distance, bool immediate = true, bool isMappedLocation = false);
void AddMovementLocation(float x, float y, float z, float speed, int16 delay, const char* lua_function, float heading, bool include_heading = false);
void ProcessMovement(bool isSpawnListLocked=false);
void ResetMovement(bool inFlight=false);
void ResetMovement();
bool ValidateRunning(bool lockMovementLocation, bool lockMovementLoop);
bool IsRunning();
void CalculateRunningLocation(bool stop = false);
@ -1506,6 +1506,7 @@ private:
int32 loot_drop_type;
std::atomic<bool> deleted_spawn;
std::atomic<bool> reset_movement;
Mutex m_GridMutex;
bool is_collector;
bool scared_by_strong_players;

View file

@ -2150,6 +2150,11 @@ bool WorldDatabase::loadCharacterProperties(Client* client) {
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_group_lock_method(val);
}
else if (!stricmp(prop_name, CHAR_PROPERTY_ASSISTAUTOATTACK))
{
int8 val = atoul(prop_value);
client->GetPlayer()->GetInfoStruct()->set_assist_auto_attack(val);
}
}
return true;

View file

@ -122,6 +122,8 @@ using namespace std;
#define CHAR_PROPERTY_GROUPSOLOAUTOLOCK "group_solo_autolock"
#define CHAR_PROPERTY_AUTOLOOTMETHOD "group_auto_loot_method"
#define CHAR_PROPERTY_ASSISTAUTOATTACK "assist_auto_attack"
#define DB_TYPE_SPELLEFFECTS 1
#define DB_TYPE_MAINTAINEDEFFECTS 2

View file

@ -726,10 +726,6 @@ void Client::SendCharInfo() {
SendControlGhost(player->GetIDWithPlayerSpawn(player), 255);
if (version <= 283) {
//le: hack to allow client time to zone in, it gets stuck on Loading UI Resources if we go too fast, need to figure it out. Probably something it doesnt like with ExamineInfo packets
Sleep(2000);
}
//sending bad spawn packet?
//SendAchievementsList();

View file

@ -2773,6 +2773,7 @@ void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, i
int droplistsize = loot_drops->size();
float chancedroptally = 0;
bool breakIterMaxLoot = false;
chancedrop = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / 100));
for (loot_drop_itr = loot_drops->begin(); loot_drop_itr != loot_drops->end(); loot_drop_itr++) {
drop = *loot_drop_itr;
@ -2825,7 +2826,14 @@ void ZoneServer::AddLoot(NPC* npc, Spawn* killer, GroupLootMethod loot_method, i
//if(drop->equip_item)
}
if (table->maxlootitems > 0 && count >= table->maxlootitems)
// so many items on this table break out and cap it!
if (table->maxlootitems > 0 && count >= table->maxlootitems) {
breakIterMaxLoot = true;
break;
}
}
// hit our max item drop for this table already, break out of numberchances
if(breakIterMaxLoot) {
break;
}
}