- Fix #496 ITEM_STAT_ABILITYCASTINGSPEED (664) and ITEM_STAT_SPELLREUSESPEED (665) now supported

- Fix #109 Soulrend does not knock down target (finish spell cast, ZoneServer::SendCastSpellPacket spell_visual is disabled) when no damage applied
* alter table character_spell_effects add column has_damaged tinyint(3) unsigned not null default 0 after resisted;

- Fix #536, SpellDamage now can drain power.  Also Fixed AoM and DoF client WS_HearSiphonSpellDamage
- SpellDamage LUA Function now returns a boolean whether damage is dealt (or spell resisted) -- (true is damage/false is no damage or resisted).  See Spells/Fighter/Crusader/Shadowknight/Soulrend.lua for a sample.
- DamageSpawn LUA Function now returns a boolean whether damage is dealt, updated to allow take_power argument DamageSpawn(Attacker, Victim, victim, type, dmg_type, low_dmg, high_dmg, spell_name, crit_mod, is_tick, no_calcs, ignore_attacker, take_power)
- new LUA Functions (both can be used in and outside of a LUA Spell):
        * SpellDamageExt(Target, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power, class_id_reqs...) -- extends support for take_power field (SpellDamage function does not have this and would break other potential spells)
        * SendHearCast(Spawn, spell_visual_id, cast_time, Caster, Target) -- lets the Spawn see a spell visual on Target.  If Caster is not defined, we use Spawn, same goes for Target.
- Fixed WS_HearHeal struct for DoF client (displays critically heal vs heal) and proper spell name.  DoF does not support absorb or other types.
- Support for translation of spell_visual (spells table) aka spellcast.dat from assets vpl.

CREATE TABLE `spell_visuals` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL,
  `alternate_spell_visual` varchar(128) DEFAULT NOT NULL '',
  `spell_visual_id` int(10) unsigned NOT NULL DEFAULT 0,
  `min_version_range` int(10) unsigned NOT NULL DEFAULT 0,
  `max_version_range` int(10) unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
** MAKE SURE TO GET spell_visuals sql included with update inserted!

- Fixed right-click inventory examine (again?) - tested and it is working for main invetory, bags and items in bags!
- DoF bags support up to 36 slots now instead of the restricted 20 for "classic" client
- DoF and classic equipment restricts to 22 slots instead of trying to send client 25 slots (the additional do not exist)
- Fix crash on signs due to lack of nullptr check on entity_command
- SetInfoStructString / GetInfoStructString now supports combat_action_state -- can be used without overriding action_state outside of combat.
This commit is contained in:
Emagi 2023-11-12 13:19:59 -05:00
parent 20795c5a6b
commit 3944d57579
30 changed files with 655 additions and 175 deletions

File diff suppressed because one or more lines are too long

View file

@ -147,7 +147,7 @@ bool BotBrain::ProcessSpell(Entity* target, float distance) {
Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
// recast time
int32 time = Timer::GetCurrentTime2() + (spell->GetSpellData()->recast * 1000);
int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
Body->SetRecast(spell, time);
string str = "I am casting ";
@ -191,7 +191,7 @@ bool BotBrain::ProcessOutOfCombatSpells() {
Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
// recast time
int32 time = Timer::GetCurrentTime2() + (spell->GetSpellData()->recast * 1000);
int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
Body->SetRecast(spell, time);
string str = "I am casting ";

View file

@ -390,13 +390,18 @@ void Entity::RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo
SetAttackDelay(false, true);
}
bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs){
bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod, bool no_calcs, int8 override_packet_type, bool take_power){
if(!victim || !luaspell || !luaspell->spell)
return false;
Spell* spell = luaspell->spell;
Skill* skill = nullptr;
int8 packet_type = DAMAGE_PACKET_TYPE_SPELL_DAMAGE;
if(override_packet_type) {
packet_type = override_packet_type;
}
int8 hit_result = 0;
bool is_tick = false; // if spell is already active, this is a tick
if (GetZone()->GetSpellProcess()->GetActiveSpells()->count(luaspell)){
@ -404,14 +409,14 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
is_tick = true;
}
else if(spell->GetSpellData()->type == SPELL_BOOK_TYPE_COMBAT_ART)
hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, false);
hit_result = DetermineHit(victim, packet_type, damage_type, 0, false, luaspell);
else
hit_result = DetermineHit(victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, 0, true, luaspell);
hit_result = DetermineHit(victim, packet_type, damage_type, 0, true, luaspell);
if(victim->IsEntity()) {
CheckEncounterState((Entity*)victim);
}
bool successful_hit = true;
if(hit_result == DAMAGE_PACKET_RESULT_SUCCESSFUL) {
luaspell->last_spellattack_hit = true;
//If this spell is a tick and has already crit, force the tick to crit
@ -421,8 +426,8 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
else
crit_mod = 2;
}
if(DamageSpawn((Entity*)victim, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, luaspell) && !luaspell->crit)
luaspell->crit = true;
DamageSpawn((Entity*)victim, packet_type, damage_type, low_damage, high_damage, spell->GetName(), crit_mod, is_tick, no_calcs, false, take_power, luaspell);
CheckProcs(PROC_TYPE_OFFENSIVE, victim);
CheckProcs(PROC_TYPE_MAGICAL_OFFENSIVE, victim);
@ -447,6 +452,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
}
}
else {
successful_hit = false;
if(hit_result == DAMAGE_PACKET_RESULT_RESIST)
luaspell->resisted = true;
if(victim->IsNPC())
@ -518,7 +524,7 @@ bool Entity::SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8
}
}
return true;
return successful_hit;
}
bool Entity::ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg) {
@ -703,6 +709,9 @@ bool Entity::SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string
}
int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell){
if(lua_spell) {
lua_spell->is_damage_spell = true;
}
if(!victim) {
return DAMAGE_PACKET_RESULT_MISS;
}
@ -957,10 +966,16 @@ Skill* Entity::GetSkillByWeaponType(int8 type, int8 damage_type, bool update) {
return 0;
}
bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, LuaSpell* spell) {
bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod, bool is_tick, bool no_calcs, bool ignore_attacker, bool take_power, LuaSpell* spell) {
if(spell) {
spell->is_damage_spell = true;
}
bool has_damaged = false;
if(!victim || !victim->Alive() || victim->GetHP() == 0)
return false;
int8 hit_result = 0;
int16 blow_type = 0;
sint32 damage = 0;
@ -1070,8 +1085,21 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
if (damage < (sint64)prevDmg)
useWards = true;
victim->TakeDamage(damage);
if(damage > 0 && spell) {
has_damaged = true;
spell->has_damaged = true;
}
if(take_power) {
sint32 curPower = victim->GetPower();
if(curPower < damage)
curPower = 0;
else
curPower -= damage;
victim->SetPower(curPower);
}
else {
victim->TakeDamage(damage);
}
victim->CheckProcs(PROC_TYPE_DAMAGED, this);
if (IsPlayer()) {
@ -1124,8 +1152,9 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
else
victim->CheckProcs(PROC_TYPE_PHYSICAL_DEFENSIVE, this);
}
return crit;
if(spell)
spell->crit = crit;
return has_damaged;
}
float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) {

View file

@ -10628,26 +10628,8 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
World::newValue = strtoull(sep->arg[1], NULL, 0);
}
else if (atoi(sep->arg[0]) == 29 && sep->IsNumber(1)) {
PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", client->GetVersion());
if (packet) {
int32 caster_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
int32 target_id = client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer());
packet->setDataByName("spawn_id", caster_id);
packet->setArrayLengthByName("num_targets", 1);
packet->setArrayDataByName("target", target_id);
packet->setDataByName("num_targets", 1);
packet->setDataByName("spell_visual", strtoull(sep->arg[1], NULL, 0)); //result
packet->setDataByName("cast_time", sep->IsNumber(2) ? strtof(sep->arg[2], NULL)*.01f : 2500); //delay
packet->setDataByName("spell_id", 1);
packet->setDataByName("spell_level", 1);
packet->setDataByName("spell_tier", 1);
EQ2Packet* outapp = packet->serialize();
DumpPacket(outapp);
client->QueuePacket(outapp);
safe_delete(packet);
}
client->SendHearCast(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(),
strtoull(sep->arg[1], NULL, 0), atoul(sep->arg[2]));
}
else if (atoi(sep->arg[0]) == 30) {
PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", client->GetVersion());
@ -10677,6 +10659,10 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
ClientPacketFunctions::SendServerControlFlags(client, param1, param2, paramval);
}
}
else if (atoi(sep->arg[0]) == 33 && sep->IsNumber(1) && sep->IsNumber(2)) {
client->GetCurrentZone()->SendHealPacket(client->GetPlayer(), client->GetPlayer()->GetTarget() ? client->GetPlayer()->GetTarget() : client->GetPlayer(),
atoul(sep->arg[1]), atoul(sep->arg[2]), "TestSpell");
}
}
else {
PacketStruct* packet2 = configReader.getStruct("WS_ExamineSpellInfo", client->GetVersion());

View file

@ -346,6 +346,7 @@ void Entity::MapInfoStruct()
get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &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);
/** SETS **/
set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@ -537,6 +538,7 @@ void Entity::MapInfoStruct()
set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &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);
}
@ -1335,7 +1337,7 @@ void Entity::CalculateBonuses(){
info->set_potency(0);
info->set_hate_mod(0);
info->set_reuse_speed(0);
info->set_casting_speed(0);
// info->set_casting_speed(0);
info->set_recovery_speed(0);
info->set_spell_reuse_speed(0);
info->set_spell_multi_attack(0);
@ -2161,7 +2163,7 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
}
if (attacker && spell->caster)
attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, spell);
attacker->DamageSpawn(spell->caster, DAMAGE_PACKET_TYPE_SPELL_DAMAGE, damage_type, redirectDamage, redirectDamage, 0, 0, false, false, false, false, spell);
}
bool shouldRemoveSpell = false;

View file

@ -471,6 +471,7 @@ struct InfoStruct{
reload_player_spells_ = oldStruct->get_reload_player_spells();
action_state_ = oldStruct->get_action_state();
combat_action_state_ = oldStruct->get_combat_action_state();
}
//mutable std::shared_mutex mutex_;
@ -681,6 +682,8 @@ struct InfoStruct{
std::string get_action_state() { std::lock_guard<std::mutex> lk(classMutex); return action_state_; }
std::string get_combat_action_state() { std::lock_guard<std::mutex> lk(classMutex); return combat_action_state_; }
void set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
void set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@ -883,11 +886,11 @@ struct InfoStruct{
void add_crit_bonus(float value) { std::lock_guard<std::mutex> lk(classMutex); if(crit_bonus_ + value < 0.0f) crit_bonus_ = 0.0f; else crit_bonus_ += value; }
void add_potency(float value) { std::lock_guard<std::mutex> lk(classMutex); if(potency_ + value < 0.0f) potency_ = 0.0f; else potency_ += value; }
void add_hate_mod(float value) { std::lock_guard<std::mutex> lk(classMutex); if(hate_mod_ + value < 0.0f) hate_mod_ = 0.0f; else hate_mod_ += value; }
void add_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(reuse_speed_ + value < 0.0f) reuse_speed_ = 0.0f; else reuse_speed_ += value; }
void add_casting_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(casting_speed_ + value < 0.0f) casting_speed_ = 0.0f; else casting_speed_ += value; }
void add_recovery_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(recovery_speed_ + value < 0.0f) recovery_speed_ = 0.0f; else recovery_speed_ += value; }
void add_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(spell_reuse_speed_ + value < 0.0f) spell_reuse_speed_ = 0.0f; else spell_reuse_speed_ += value; }
void add_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); if(spell_multi_attack_ + value < 0.0f) spell_multi_attack_ = 0.0f; else spell_multi_attack_ += value; }
void add_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); reuse_speed_ += value; }
void add_casting_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); casting_speed_ += value; }
void add_recovery_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); recovery_speed_ += value; }
void add_spell_reuse_speed(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_reuse_speed_ += value; }
void add_spell_multi_attack(float value) { std::lock_guard<std::mutex> lk(classMutex); spell_multi_attack_ += value; }
void add_dps(float value) { std::lock_guard<std::mutex> lk(classMutex); if(dps_ + value < 0.0f) dps_ = 0.0f; else dps_ += value; }
void add_dps_multiplier(float value) { std::lock_guard<std::mutex> lk(classMutex); if(dps_multiplier_ + value < 0.0f) dps_multiplier_ = 0.0f; else dps_multiplier_ += value; }
void add_attackspeed(float value) { std::lock_guard<std::mutex> lk(classMutex); if(attackspeed_ + value < 0.0f) attackspeed_ = 0.0f; else attackspeed_ += value; }
@ -975,6 +978,8 @@ struct InfoStruct{
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; }
void ResetEffects(Spawn* spawn)
{
for(int i=0;i<45;i++){
@ -1183,6 +1188,7 @@ private:
int8 reload_player_spells_;
std::string action_state_;
std::string combat_action_state_;
// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
std::mutex classMutex;
@ -1424,13 +1430,13 @@ public:
bool RangeWeaponReady();
void MeleeAttack(Spawn* victim, float distance, bool primary, bool multi_attack = false);
void RangeAttack(Spawn* victim, float distance, Item* weapon, Item* ammo, bool multi_attack = false);
bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false);
bool SpellAttack(Spawn* victim, float distance, LuaSpell* luaspell, int8 damage_type, int32 low_damage, int32 high_damage, int8 crit_mod = 0, bool no_calcs = false, int8 override_packet_type = 0, bool take_power = false);
bool ProcAttack(Spawn* victim, int8 damage_type, int32 low_damage, int32 high_damage, string name, string success_msg, string effect_msg);
bool SpellHeal(Spawn* target, float distance, LuaSpell* luaspell, string heal_type, int32 low_heal, int32 high_heal, int8 crit_mod = 0, bool no_calcs = false, string custom_spell_name="");
int8 DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHitBonus, bool is_caster_spell, LuaSpell* lua_spell = nullptr);
float GetDamageTypeResistPercentage(int8 damage_type);
Skill* GetSkillByWeaponType(int8 type, int8 damage_type, bool update);
bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, LuaSpell* spell = 0);
bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, bool take_power = false, LuaSpell* spell = 0);
float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false);
void AddHate(Entity* attacker, sint32 hate);
bool CheckInterruptSpell(Entity* attacker);

View file

@ -2098,7 +2098,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
tradeskill_class_levels[i] = tmp_level;
}
}
bool simplified_display = false;
if (client->GetVersion() <= 546) { //simplify display (if possible)
map<int8, int16> new_adv_class_levels;
for (int i = 1; i <= 31; i += 10) {
@ -2115,7 +2114,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
}
}
if (new_adv_class_levels.size() > 0) {
simplified_display = true;
int8 i = 0;
for (itr = new_adv_class_levels.begin(); itr != new_adv_class_levels.end(); itr++) {
i = itr->first;
@ -2302,9 +2300,11 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
}
case ITEM_TYPE_BAG:{
if(bag_info){
int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
if (bag_info->num_slots > max_slots)
bag_info->num_slots = max_slots;
if (client->GetVersion() <= 546) {
if (bag_info->num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
bag_info->num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
packet->setSubstructDataByName("details", "num_slots", bag_info->num_slots);
packet->setSubstructDataByName("details", "weight_reduction", bag_info->weight_reduction);
}
@ -2375,12 +2375,14 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
packet->setSubstructDataByName("header_info", "footer_type", 0);
spell->SetPacketInformation(packet, player->GetZone()->GetClientBySpawn(player));
if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true))
if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) {
packet->setDataByName("scribed", 1);
}
if (packet->GetVersion() >= 927){
if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true))
if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) {
packet->setAddToPacketByName("scribed_better_version", 1);// need to confirm
}
}
else
packet->setAddToPacketByName("scribed_better_version", 0); //if not scribed
@ -2576,7 +2578,6 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
packet->setSubstructDataByName("footer", "description", description.c_str());
LogWrite(ITEM__PACKET, 0, "Items", "Dump/Print Packet in func: %s, line: %i", __FUNCTION__, __LINE__);
#if EQDEBUG >= 9
packet->PrintPacket();
#endif
@ -3547,9 +3548,12 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
menu_data -= ITEM_MENU_TYPE_GENERIC;
if (item->details.num_slots > 0) {
if (packet->GetVersion() <= 546 && item->details.num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
item->details.num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
int8 max_slots = player->GetMaxBagSlots(client->GetVersion());
if (item->details.num_slots > max_slots)
item->details.num_slots = max_slots;
menu_data += ITEM_MENU_TYPE_BAG;
if (item->details.num_free_slots == item->details.num_slots)
menu_data += ITEM_MENU_TYPE_EMPTY_BAG;
}
@ -3653,24 +3657,24 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
packet->setSubstructArrayDataByName("items", "unique_id", item->details.item_id, 0, i);
else
packet->setSubstructArrayDataByName("items", "unique_id", item->details.unique_id, 0, i);
packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i);
packet->setSubstructArrayDataByName("items", "inv_slot_id", item->details.inv_slot_id, 0, i);
packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
if (overflow) {
if (overflow)
packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i);
}
else {
if(i < 6) {
packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id ? item->details.bag_id : i, 0, i);
packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
if(packet->GetVersion() <= 546) {
/* DoF client and earlier side automatically assigns indexes
** we have to send 0xFF or else all index is set to 255 on client
** and then examine inventory won't work */
packet->setSubstructArrayDataByName("items", "index", 0xFF, 0, i);
}
else {
packet->setSubstructArrayDataByName("items", "bag_id", item->details.bag_id, 0, i);
packet->setSubstructArrayDataByName("items", "index", i, 0, i);
packet->setSubstructArrayDataByName("items", "index", i, 0, i);
}
item->details.index = i;
}
item->details.index = i;
packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i); // inventory doesn't convert slots
if (client->GetVersion() <= 1208) {
@ -3692,11 +3696,6 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
packet->setSubstructArrayDataByName("items", "item_id", item->details.item_id, 0, i);
//need broker id
packet->setSubstructArrayDataByName("items", "name", item->name.c_str(), 0, i);
}
@ -4147,7 +4146,7 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
PacketStruct* packet2 = configReader.getStruct("Substruct_Item", version);
packet_size = packet2->GetTotalPacketSize();
safe_delete(packet2);
int8 num_slots = NUM_SLOTS;
int8 num_slots = player->GetNumSlotsEquip(version);
packet->setArrayLengthByName("item_count", num_slots);
if(!orig_packet){
xor_packet = new uchar[packet_size* num_slots];
@ -4161,7 +4160,7 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
int32 levelsLowered = (effective_level > 0 && effective_level < player->GetLevel()) ? player->GetLevel() - effective_level : 0;
for(int16 i=0;i<NUM_SLOTS;i++){
for(int16 i=0;i<num_slots;i++){
// override the item slot we currently check as the client has different ordering, we need to match it
int16 itemIdx = player->ConvertSlotFromClient(i, version);
@ -4171,9 +4170,12 @@ EQ2Packet* EquipmentItemList::serialize(int16 version, Player* player){
if(item->slot_data.size() > 0)
menu_data -= ITEM_MENU_TYPE_GENERIC;
if (item->details.num_slots > 0) {
if (packet->GetVersion() <= 546 && item->details.num_slots > CLASSIC_EQ_MAX_BAG_SLOTS)
item->details.num_slots = CLASSIC_EQ_MAX_BAG_SLOTS;
int8 max_slots = player->GetMaxBagSlots(version);
if (item->details.num_slots > max_slots)
item->details.num_slots = max_slots;
menu_data += ITEM_MENU_TYPE_BAG;
if (item->details.num_free_slots == item->details.num_slots)
menu_data += ITEM_MENU_TYPE_EMPTY_BAG;
}

View file

@ -24,7 +24,6 @@
#include <ctime>
#include "../../common/types.h"
#include "../../common/DataBuffer.h"
#include "../../common/MiscFunctions.h"
#include "../Commands/Commands.h"
#include "../../common/ConfigReader.h"
@ -111,8 +110,10 @@ extern MasterItemList master_item_list;
#define DOF_DRINK_SLOT 2097152
#define CLASSIC_EQ_MAX_BAG_SLOTS 20
#define DOF_EQ_MAX_BAG_SLOTS 36
#define NUM_BANK_SLOTS 12
#define NUM_SHARED_BANK_SLOTS 8
#define CLASSIC_NUM_SLOTS 22
#define NUM_SLOTS 25
#define NUM_INV_SLOTS 6
#define INV_SLOT1 0
@ -204,8 +205,8 @@ extern MasterItemList master_item_list;
#define ITEM_MENU_TYPE_GENERIC 1 //0
#define ITEM_MENU_TYPE_EQUIP 2 //1
#define ITEM_MENU_TYPE_GENERIC 1 //0 (NON_EQUIPABLE)
#define ITEM_MENU_TYPE_EQUIP 2 //1 (This is SLOT_FULL for classic)
#define ITEM_MENU_TYPE_BAG 4//2
#define ITEM_MENU_TYPE_HOUSE 8 //3 Place
#define ITEM_MENU_TYPE_EMPTY_BAG 16 //4

View file

@ -1854,7 +1854,8 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
if(!luaspell || luaspell->resisted) {
lua_interface->ResetFunctionStack(state);
return 0;
lua_interface->SetBooleanValue(state, false);
return 1;
}
Spawn* caster = luaspell->caster;
sint32 type = lua_interface->GetSInt32Value(state, 2);
@ -1936,6 +1937,7 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
success = true;
}
}
lua_interface->SetBooleanValue(state, luaspell->has_damaged);
if (success) {
Spell* spell = luaspell->spell;
if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art
@ -1945,7 +1947,118 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
}
}
}
return 0;
else {
lua_interface->SetBooleanValue(state, false);
}
return 1;
}
int EQ2Emu_lua_SpellDamageExt(lua_State* state) {
if (!lua_interface)
return 0;
Spawn* target = lua_interface->GetSpawn(state);
LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
if(!luaspell || luaspell->resisted) {
lua_interface->ResetFunctionStack(state);
lua_interface->SetBooleanValue(state, false);
return 1;
}
Spawn* caster = luaspell->caster;
sint32 type = lua_interface->GetSInt32Value(state, 2);
int32 min_damage = lua_interface->GetInt32Value(state, 3);
int32 max_damage = lua_interface->GetInt32Value(state, 4);
int8 crit_mod = lua_interface->GetInt32Value(state, 5);
bool no_calcs = lua_interface->GetInt32Value(state, 6) == 1;
int32 override_packet_type = lua_interface->GetInt32Value(state, 7);
bool take_power = lua_interface->GetInt32Value(state, 8) == 1;
//lua_interface->ResetFunctionStack(state);
int32 class_id = lua_interface->GetInt32Value(state, 9);
vector<int16> faction_req;
vector<int16> race_req;
int32 class_req = 0;
int32 i = 0;
int8 f = 0;
int8 r = 0;
while ((class_id = lua_interface->GetInt32Value(state, 9 + i))) {
if (class_id < 100) {
class_req += pow(2.0, double(class_id - 1));
}
else if (class_id > 100 && class_id < 1000) {
race_req.push_back(class_id);
r++;
}
else {
faction_req.push_back(class_id);
f++;
}
i++;
}
lua_interface->ResetFunctionStack(state);
if (caster && caster->IsEntity()) {
bool race_match = false;
bool success = false;
luaspell->resisted = false;
if (luaspell->targets.size() > 0) {
ZoneServer* zone = luaspell->caster->GetZone();
Spawn* target = 0;
luaspell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
for (int32 i = 0; i < luaspell->targets.size(); i++) {
if ((target = zone->GetSpawnByID(luaspell->targets[i]))) {
if (race_req.size() > 0) {
for (int8 i = 0; i < race_req.size(); i++) {
if(race_req[i] == target->GetRace() ||
race_req[i] == race_types_list.GetRaceType(target->GetModelType()) ||
race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) {
race_match = true;
}
}
}
else
race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true
if (race_match == true) {
float distance = caster->GetDistance(target, true);
((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power);
}
}
}
success = true;
luaspell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
}
else if (target) {
//check class and race/faction here
if (race_req.size() > 0) {
for (int8 i = 0; i < race_req.size(); i++) {
if(race_req[i] == target->GetRace() ||
race_req[i] == race_types_list.GetRaceType(target->GetModelType()) ||
race_req[i] == race_types_list.GetRaceBaseType(target->GetModelType())) {
race_match = true;
}
}
}
else
race_match = true; // if the race_req.size = 0 then there is no race requirement and the race_match will be true
if (race_match == true) {
float distance = caster->GetDistance(target, true);
if (((Entity*)caster)->SpellAttack(target, distance, luaspell, type, min_damage, max_damage, crit_mod, no_calcs, override_packet_type, take_power))
success = true;
}
}
lua_interface->SetBooleanValue(state, luaspell->has_damaged);
if (success) {
Spell* spell = luaspell->spell;
if (caster->IsPlayer() && spell && spell->GetSpellData()->target_type == 1 && spell->GetSpellData()->spell_book_type == 1) { //offense combat art
((Player*)caster)->InCombat(true);
if (caster->GetZone())
caster->GetZone()->TriggerCharSheetTimer();
}
}
}
else {
lua_interface->SetBooleanValue(state, false);
}
return 1;
}
int EQ2Emu_lua_ModifyPower(lua_State* state) {
if (!lua_interface)
@ -12424,30 +12537,39 @@ int EQ2Emu_lua_DamageSpawn(lua_State* state) {
bool is_tick = (lua_interface->GetInt8Value(state, 9) == 1);
bool no_calcs = (lua_interface->GetInt8Value(state, 10) == 1);
bool ignore_attacker = (lua_interface->GetInt8Value(state, 11) == 1);
bool take_power = (lua_interface->GetInt8Value(state, 12) == 1);
lua_interface->ResetFunctionStack(state);
if (!attacker) {
lua_interface->LogError("%s: LUA ProcDamage command error: caster is not a valid spawn", lua_interface->GetScriptName(state));
return 0;
lua_interface->SetBooleanValue(state, false);
return 1;
}
if (!attacker->IsEntity()) {
lua_interface->LogError("%s: LUA ProcDamage command error: caster is not an entity", lua_interface->GetScriptName(state));
return 0;
lua_interface->SetBooleanValue(state, false);
return 1;
}
if (!victim) {
lua_interface->LogError("%s: LUA ProcDamage command error: target is not a valid spawn", lua_interface->GetScriptName(state));
return 0;
lua_interface->SetBooleanValue(state, false);
return 1;
}
if (!victim->IsEntity()) {
lua_interface->LogError("%s: LUA ProcDamage command error: target is not an entity", lua_interface->GetScriptName(state));
return 0;
lua_interface->SetBooleanValue(state, false);
return 1;
}
((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker);
return 0;
bool has_damaged = ((Entity*)attacker)->DamageSpawn((Entity*)victim, type, dmg_type, low_damage, high_damage, spell_name.c_str(), crit_mod, is_tick, no_calcs, ignore_attacker, take_power);
lua_interface->SetBooleanValue(state, has_damaged);
return 1;
}
int EQ2Emu_lua_IsInvulnerable(lua_State* state) {
@ -13878,3 +14000,30 @@ int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state) {
lua_interface->SetBooleanValue(state, true);
return 1;
}
int EQ2Emu_lua_SendHearCast(lua_State* state) {
if (!lua_interface)
return 0;
LuaSpell* spell = lua_interface->GetCurrentSpell(state);
Spawn* spawn = lua_interface->GetSpawn(state);
int32 spell_visual_id = lua_interface->GetInt32Value(state, 2);
int16 cast_time = lua_interface->GetInt16Value(state, 3);
Spawn* caster = lua_interface->GetSpawn(state, 4);
Spawn* target = lua_interface->GetSpawn(state, 5);
lua_interface->ResetFunctionStack(state);
if(spell && spawn && spawn->IsEntity()) {
ZoneServer* zone = spawn->GetZone();
if(zone) {
zone->SendCastSpellPacket(spell, caster && caster->IsEntity() ? (Entity*)caster : (Entity*)spawn, spell_visual_id, cast_time > 0 ? cast_time : 0xFFFF);
}
}
else if (spawn) {
if (spawn->IsPlayer()) {
Client* client = ((Player*)spawn)->GetClient();
if (client) {
client->SendHearCast(caster ? caster : client->GetPlayer(), target ? target : client->GetPlayer(), spell_visual_id, cast_time);
}
}
}
return 0;
}

View file

@ -183,6 +183,7 @@ int EQ2Emu_lua_Spawn(lua_State* state);
int EQ2Emu_lua_AddSpawnAccess(lua_State* state);
int EQ2Emu_lua_CastSpell(lua_State* state);
int EQ2Emu_lua_SpellDamage(lua_State* state);
int EQ2Emu_lua_SpellDamageExt(lua_State* state);
int EQ2Emu_lua_FaceTarget(lua_State* state);
int EQ2Emu_lua_MoveToLocation(lua_State* state);
int EQ2Emu_lua_ClearRunningLocations(lua_State* state);
@ -646,4 +647,6 @@ int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state);
int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state);
int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state);
int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state);
int EQ2Emu_lua_SendHearCast(lua_State* state);
#endif

View file

@ -267,6 +267,8 @@ bool LuaInterface::LoadLuaSpell(const char* name) {
spell->caster = 0;
spell->initial_target = 0;
spell->resisted = false;
spell->has_damaged = false;
spell->is_damage_spell = false;
spell->interrupted = false;
spell->last_spellattack_hit = false;
spell->crit = false;
@ -987,6 +989,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state, "GetSpeed", EQ2Emu_lua_GetSpeed);
lua_register(state, "HasMoved", EQ2Emu_lua_HasMoved);
lua_register(state, "SpellDamage", EQ2Emu_lua_SpellDamage);
lua_register(state, "SpellDamageExt", EQ2Emu_lua_SpellDamageExt);
lua_register(state, "CastSpell", EQ2Emu_lua_CastSpell);
lua_register(state, "SpellHeal", EQ2Emu_lua_SpellHeal);
lua_register(state, "SpellHealPct", EQ2Emu_lua_SpellHealPct);
@ -1516,6 +1519,8 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state, "ReplaceWidgetFromClient", EQ2Emu_lua_ReplaceWidgetFromClient);
lua_register(state, "RemoveWidgetFromSpawnMap", EQ2Emu_lua_RemoveWidgetFromSpawnMap);
lua_register(state, "RemoveWidgetFromZoneMap", EQ2Emu_lua_RemoveWidgetFromZoneMap);
lua_register(state, "SendHearCast", EQ2Emu_lua_SendHearCast);
}
void LuaInterface::LogError(const char* error, ...) {
@ -1980,6 +1985,8 @@ LuaSpell* LuaInterface::GetSpell(const char* name) {
new_spell->timer = spell->timer;
new_spell->timer.Disable();
new_spell->resisted = false;
new_spell->is_damage_spell = false;
new_spell->has_damaged = false;
new_spell->interrupted = false;
new_spell->crit = false;
new_spell->last_spellattack_hit = false;

View file

@ -91,6 +91,8 @@ struct LuaSpell{
int8 slot_pos;
int32 damage_remaining;
bool resisted;
bool has_damaged;
bool is_damage_spell;
bool interrupted;
bool crit;
bool last_spellattack_hit;

View file

@ -1396,6 +1396,25 @@ int16 Player::ConvertSlotFromClient(int8 slot, int16 version) {
return slot;
}
int16 Player::GetNumSlotsEquip(int16 version) {
if(version <= 546) {
return CLASSIC_NUM_SLOTS;
}
return NUM_SLOTS;
}
int8 Player::GetMaxBagSlots(int16 version) {
if(version <= 283) {
return CLASSIC_EQ_MAX_BAG_SLOTS;
}
else if(version = 546) {
return DOF_EQ_MAX_BAG_SLOTS;
}
return 255;
}
vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type, bool send_item_updates) {
vector<EQ2Packet*> packets;
EquipmentItemList* equipList = &equipment_list;
@ -1403,7 +1422,7 @@ vector<EQ2Packet*> Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in
if(appearance_type)
equipList = &appearance_equipment_list;
if(index >= NUM_SLOTS) {
if(index >= GetNumSlotsEquip(version)) {
LogWrite(PLAYER__ERROR, 0, "Player", "%u index is out of range for equip items, bag_id: %i, slot: %u, version: %u, appearance: %u", index, bag_id, slot, version, appearance_type);
return packets;
}
@ -2653,33 +2672,26 @@ vector<Spell*> Player::GetSpellBookSpellsByTimer(int32 timerID) {
}
void Player::ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
if (modify_recast) {
spell->recast = recast;
spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
}
SetSpellEntryRecast(spell, modify_recast, recast);
if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
spell->status += value; // use set/remove spell status now
}
}
void Player::AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
if (modify_recast) {
spell->recast = recast;
spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
}
SetSpellEntryRecast(spell, modify_recast, recast);
if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
spell->status = spell->status | value;
}
}
void Player::RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast, int16 recast) {
if (modify_recast) {
spell->recast = recast;
spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
}
SetSpellEntryRecast(spell, modify_recast, recast);
if (modify_recast || spell->recast_available <= Timer::GetCurrentTime2() || value == 4) {
spell->status = spell->status & ~value;
}
}
void Player::SetSpellStatus(Spell* spell, int8 status){
MSpellsBook.lock();
vector<SpellBookEntry*>::iterator itr;
@ -2694,6 +2706,21 @@ void Player::SetSpellStatus(Spell* spell, int8 status){
MSpellsBook.unlock();
}
void Player::SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast) {
if (modify_recast) {
spell->recast = recast;
float override_recast = static_cast<float>(recast);
Spell* spell_ = master_spell_list.GetSpell(spell->spell_id, spell->tier);
if(spell_) {
int32 recast_time = spell_->CalculateRecastTimer(this, override_recast);
spell->recast_available = Timer::GetCurrentTime2() + (recast_time / 10);
}
else {
spell->recast_available = Timer::GetCurrentTime2() + (recast * 100);
}
}
}
vector<SpellBookEntry*>* Player::GetSpellsSaveNeeded(){
vector<SpellBookEntry*>* ret = 0;
vector<SpellBookEntry*>::iterator itr;
@ -6947,13 +6974,13 @@ void Player::SaveSpellEffects()
continue;
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT,
"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')",
"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')",
database.getSafeEscapeString(info->spell_effects[i].spell->spell->GetName()).c_str(), caster_char_id,
target_char_id, 0 /*no target_type for spell_effects*/, DB_TYPE_SPELLEFFECTS /* db_effect_type for spell_effects */, info->spell_effects[i].spell->spell->IsCopiedSpell() ? info->spell_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->spell_effects[i].spell_id, i, info->spell_effects[i].spell->slot_pos,
info->spell_effects[i].icon, info->spell_effects[i].icon_backdrop, 0 /* no conc_used for spell_effects */, info->spell_effects[i].tier,
info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(),
info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers,
info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str());
info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, info->spell_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str());
}
if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
@ -6975,12 +7002,12 @@ void Player::SaveSpellEffects()
if(info->maintained_effects[i].spell->spell->GetSpellData() && !info->maintained_effects[i].spell->spell->GetSpellData()->duration_until_cancel)
timestamp = info->maintained_effects[i].expire_timestamp - Timer::GetCurrentTime2();
savedEffects.AddQueryAsync(GetCharacterID(), &database, Q_INSERT,
"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')",
"insert into character_spell_effects (name, caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, charid, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function) values ('%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %f, %u, '%s', %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, '%s')",
database.getSafeEscapeString(info->maintained_effects[i].name).c_str(), caster_char_id, target_char_id, info->maintained_effects[i].target_type, DB_TYPE_MAINTAINEDEFFECTS /* db_effect_type for maintained_effects */, info->maintained_effects[i].spell->spell->IsCopiedSpell() ? info->maintained_effects[i].spell->spell->GetSpellData()->inherited_spell_id : info->maintained_effects[i].spell_id, i, info->maintained_effects[i].slot_pos,
info->maintained_effects[i].icon, info->maintained_effects[i].icon_backdrop, info->maintained_effects[i].conc_used, info->maintained_effects[i].tier,
info->maintained_effects[i].total_time, timestamp, database.getSafeEscapeString(info->maintained_effects[i].spell->file_name.c_str()).c_str(), info->maintained_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(),
info->maintained_effects[i].spell->damage_remaining, info->maintained_effects[i].spell->effect_bitmask, info->maintained_effects[i].spell->num_triggers, info->maintained_effects[i].spell->had_triggers, info->maintained_effects[i].spell->cancel_after_all_triggers,
info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str());
info->maintained_effects[i].spell->crit, info->maintained_effects[i].spell->last_spellattack_hit, info->maintained_effects[i].spell->interrupted, info->maintained_effects[i].spell->resisted, info->maintained_effects[i].spell->has_damaged, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->maintained_effects[i].spell).c_str()).c_str());
info->maintained_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values ");

View file

@ -513,6 +513,8 @@ public:
vector<EQ2Packet*> UnequipItem(int16 index, sint32 bag_id, int8 slot, int16 version, int8 appearance_type = 0, bool send_item_updates = true);
int16 ConvertSlotToClient(int8 slot, int16 version);
int16 ConvertSlotFromClient(int8 slot, int16 version);
int16 GetNumSlotsEquip(int16 version);
int8 GetMaxBagSlots(int16 version);
EQ2Packet* SwapEquippedItems(int8 slot1, int8 slot2, int16 version, int16 equiptype);
EQ2Packet* RemoveInventoryItem(int8 bag_slot, int8 slot);
EQ2Packet* SendInventoryUpdate(int16 version);
@ -1181,6 +1183,7 @@ private:
void ModifySpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void AddSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void RemoveSpellStatus(SpellBookEntry* spell, sint16 value, bool modify_recast = true, int16 recast = 0);
void SetSpellEntryRecast(SpellBookEntry* spell, bool modify_recast, int16 recast);
void InitXPTable();
map<int8, int32> m_levelXPReq;

View file

@ -301,7 +301,7 @@ void Sign::HandleUse(Client* client, string command)
//devn00b: Add support for marking objects
if (strcmp(entity_command->command.c_str(), "mark") == 0) {
if (entity_command && strcmp(entity_command->command.c_str(), "mark") == 0) {
LogWrite(SIGN__DEBUG, 0, "Sign", "ActivateMarkReqested Sign - Command: '%s' (Should read mark)", entity_command->command.c_str());
int32 char_id = client->GetCharacterID();
database.SaveSignMark(char_id, GetWidgetID(), database.GetCharacterName(char_id), client);

View file

@ -2392,21 +2392,27 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
else
packet->setDataByName("soga_model_type", sogaModelType);
if (GetTempActionState() >= 0)
packet->setDataByName("action_state", GetTempActionState());
else {
int16 action_state = appearance.action_state;
if(IsEntity()) {
std::string actionState = "";
if (GetTempActionState() >= 0) {
action_state = GetTempActionState();
actionState = ((Entity*)this)->GetInfoStruct()->get_combat_action_state();
}
else {
actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
}
Client* client = spawn->GetClient();
int16 action_state = appearance.action_state;
if(IsEntity() && client) {
std::string actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
if(actionState.size() > 0) {
Emote* emote = visual_states.FindEmote(actionState, client->GetVersion());
if(emote != NULL)
action_state = emote->GetVisualState();
}
}
packet->setDataByName("action_state", action_state);
}
packet->setDataByName("action_state", action_state);
bool scaredOfPlayer = false;

View file

@ -1091,11 +1091,13 @@ public:
bool HasMovementLoop(){ return movement_loop.size() > 0; }
bool HasMovementLocations() {
bool hasLocations = false;
if (MMovementLocations)
if (MMovementLocations) {
MMovementLocations->readlock(__FUNCTION__, __LINE__);
}
hasLocations = movement_locations ? movement_locations->size() > 0 : false;
if (MMovementLocations)
if (MMovementLocations) {
MMovementLocations->releasereadlock(__FUNCTION__, __LINE__);
}
return hasLocations;
}

View file

@ -307,10 +307,15 @@ void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_overrid
else
timer->client = 0;
timer->spell = spell;
if(timer_override == 0)
timer->timer = new Timer((int32)(spell->GetSpellData()->recast*1000));
else
timer->timer = new Timer((int32)(timer_override*1000));
int32 recast_time = spell->GetSpellData()->recast * 1000;
if(timer_override == 0) {
recast_time = spell->CalculateRecastTimer(caster);
timer->timer = new Timer(recast_time);
}
else {
recast_time = spell->CalculateRecastTimer(caster, timer_override);
timer->timer = new Timer(recast_time);
}
timer->type_group_spell_id = spell->GetSpellData()->type_group_spell_id;
timer->linked_timer = spell->GetSpellData()->linked_timer;
@ -318,11 +323,7 @@ void SpellProcess::CheckRecast(Spell* spell, Entity* caster, float timer_overrid
recast_timers.Add(timer);
if(caster->IsPlayer()){
if(timer_override == 0)
((Player*)caster)->LockSpell(spell, (int16)(spell->GetSpellData()->recast * 10));
else
((Player*)caster)->LockSpell(spell, timer_override * 10);
((Player*)caster)->LockSpell(spell, (int16)(recast_time / 100));
if (check_linked_timers && spell->GetSpellData()->linked_timer > 0) {
vector<Spell*> linkedSpells = ((Player*)caster)->GetSpellBookSpellsByTimer(spell->GetSpellData()->linked_timer);
for (int8 i = 0; i < linkedSpells.size(); i++) {
@ -620,11 +621,13 @@ bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, boo
lua_interface->ResetFunctionStack(lua_spell->state);
}
bool result = CastProcessedSpell(lua_spell, passive);
caster->GetZone()->SendCastSpellPacket(lua_spell, caster);
if (!remove)
return CastProcessedSpell(lua_spell, passive);
if(!remove)
return result;
lua_interface->RemoveSpell(lua_spell, true, SpellScriptTimersHasSpell(lua_spell));
return true;
}
@ -651,12 +654,12 @@ void SpellProcess::SendFinishedCast(LuaSpell* spell, Client* client){
UnlockAllSpells(client);
if(spell->resisted && spell->spell->GetSpellData()->recast > 0)
CheckRecast(spell->spell, client->GetPlayer(), 0.5); // half sec recast on resisted spells
CheckRecast(spell->spell, client->GetPlayer(), 0.5f); // half sec recast on resisted spells
else if (!spell->interrupted)
CheckRecast(spell->spell, client->GetPlayer());
else if(spell->caster && spell->caster->IsPlayer())
{
((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->GetSpellData()->recast * 10));
((Player*)spell->caster)->LockSpell(spell->spell, (int16)(spell->spell->CalculateRecastTimer(spell->caster) / 100));
}
PacketStruct* packet = configReader.getStruct("WS_FinishCastSpell", client->GetVersion());
if(packet){

View file

@ -810,7 +810,7 @@ void Spell::SetPacketInformation(PacketStruct* packet, Client* client, bool disp
else {
packet->setSubstructDataByName("spell_info", "cast_time", spell->cast_time);
}
packet->setSubstructDataByName("spell_info", "recast", spell->recast);
packet->setSubstructDataByName("spell_info", "recast", CalculateRecastTimer(client->GetPlayer())/1000);
packet->setSubstructDataByName("spell_info", "radius", spell->radius);
packet->setSubstructDataByName("spell_info", "req_concentration", spell->req_concentration);
//packet->setSubstructDataByName("spell_info","req_concentration2", 2);
@ -2220,14 +2220,42 @@ bool Spell::IsCopiedSpell() {
void Spell::ModifyCastTime(Entity* caster){
int16 cast_time = spell->cast_time;
float cast_speed = caster->GetInfoStruct()->get_casting_speed();
if (cast_time > 0){
if (cast_speed > 0) // casting speed can only reduce up to half a cast time
spell->cast_time *= max((float) 0.5, (float) (1 / (1 + (cast_speed * .01))));
else if (cast_speed < 0) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
spell->cast_time *= min((float) 1.5, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
if (cast_speed > 0.0f){
bool modifiedSpeed = false;
if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time
spell->cast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01))));
else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
spell->cast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
if(modifiedSpeed) {
LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s cast time %u to %u based on cast_speed %f", GetName(), caster->GetName(), cast_time, spell->cast_time, cast_speed);
}
}
}
int32 Spell::CalculateRecastTimer(Entity* caster, float override_timer) {
int32 original_recast = static_cast<int>(GetSpellData()->recast) * 1000;
if(override_timer > 0.0f) {
original_recast = static_cast<int>(override_timer) * 1000;
}
int32 recast_time = original_recast;
float cast_speed = caster->GetInfoStruct()->get_spell_reuse_speed();
if (cast_speed > 0.0f){
bool modifiedSpeed = false;
if (cast_speed > 0.0f && (modifiedSpeed = true)) // casting speed can only reduce up to half a cast time
recast_time *= max(0.5f, (float) (1 / (1 + (cast_speed * .01))));
else if (cast_speed < 0.0f && (modifiedSpeed = true)) // not sure if casting speed debuff is capped on live or not, capping at 1.5 * the normal rate for now
recast_time *= min(1.5f, (float) (1 + (1 - (1 / (1 + (cast_speed * -.01))))));
if(modifiedSpeed) {
LogWrite(SPELL__DEBUG, 9, "Spells", "%s: spell %s recast time %u to %u based on spell_reuse_time %f", GetName(), caster->GetName(), original_recast, recast_time, cast_speed);
}
}
return recast_time;
}
vector <SpellDisplayEffect*>* Spell::GetSpellEffects(){
std::shared_lock lock(MSpellInfo);
vector <SpellDisplayEffect*>* ret = &effects;

View file

@ -364,6 +364,7 @@ public:
bool IsOffenseSpell();
bool IsCopiedSpell();
void ModifyCastTime(Entity* caster);
int32 CalculateRecastTimer(Entity* caster, float override_timer = 0.0f);
bool CastWhileStunned();
bool CastWhileMezzed();
bool CastWhileStifled();

View file

@ -61,7 +61,7 @@ public:
if(in_targeted_message)
targeted_message = string(in_targeted_message);
}
int GetVisualState() { return visual_state; }
int32 GetVisualState() { return visual_state; }
const char* GetName() { return name.c_str(); }
const char* GetMessage() { return message.c_str(); }
const char* GetTargetedMessage() { return targeted_message.c_str(); }
@ -70,7 +70,7 @@ public:
string GetMessageString() { return message; }
string GetTargetedMessageString() { return targeted_message; }
private:
int visual_state;
int32 visual_state;
string name;
string message;
string targeted_message;
@ -98,7 +98,7 @@ public:
}
void AddVersionRange(int32 min_version, int32 max_version,
char* in_name, int in_visual_state, char* in_message, char* in_targeted_message)
char* in_name, int in_visual_state, char* in_message = nullptr, char* in_targeted_message = nullptr)
{
map<VersionRange*, Emote*>::iterator itr = FindVersionRange(min_version, max_version);
if (itr != version_map.end())
@ -164,8 +164,9 @@ public:
void Reset(){
ClearVisualStates();
ClearEmotes();
ClearSpellVisuals();
}
void ClearEmotes(){
map<string, EmoteVersionRange*>::iterator map_list;
for(map_list = emoteMap.begin(); map_list != emoteMap.end(); map_list++ )
@ -215,8 +216,67 @@ public:
}
return 0;
}
void InsertSpellVisualRange(EmoteVersionRange* emote, int32 spell_visual_id) {
spellMap[emote->GetName()] = emote;
spellMapID[spell_visual_id] = emote;
}
EmoteVersionRange* FindSpellVisualRange(string var) {
if (spellMap.count(var) > 0)
{
return spellMap[var];
}
return 0;
}
EmoteVersionRange* FindSpellVisualRangeByID(int32 id) {
if (spellMapID.count(id) > 0)
{
return spellMapID[id];
}
return 0;
}
Emote* FindSpellVisual(string var, int32 version){
if (spellMap.count(var) > 0)
{
map<VersionRange*,Emote*>::iterator itr = spellMap[var]->FindEmoteVersion(version);
if (itr != spellMap[var]->GetRangeEnd())
{
Emote* emote = itr->second;
return emote;
}
}
return 0;
}
Emote* FindSpellVisualByID(int32 visual_id, int32 version){
if (spellMapID.count(visual_id) > 0)
{
map<VersionRange*,Emote*>::iterator itr = spellMapID[visual_id]->FindEmoteVersion(version);
if (itr != spellMapID[visual_id]->GetRangeEnd())
{
Emote* emote = itr->second;
return emote;
}
}
return 0;
}
void ClearSpellVisuals(){
map<string, EmoteVersionRange*>::iterator map_list;
for(map_list = spellMap.begin(); map_list != spellMap.end(); map_list++ )
safe_delete(map_list->second);
spellMap.clear();
spellMapID.clear();
}
private:
map<string,VisualState*> visualStateMap;
map<string,EmoteVersionRange*> emoteMap;
map<string,EmoteVersionRange*> spellMap;
map<int32,EmoteVersionRange*> spellMapID;
};

View file

@ -459,11 +459,29 @@ void WorldDatabase::LoadVisualStates()
visual_states.InsertEmoteRange(range);
}
range->AddVersionRange(atoul(row[4]),atoul(row[5]), row[0], atoi(row[1]), row[2], row[3]);
range->AddVersionRange(atoul(row[4]),atoul(row[5]), row[0], atoul(row[1]), row[2], row[3]);
total++;
LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row[1], atoi(row[0]));
LogWrite(WORLD__DEBUG, 5, "World", "---Loading emote state: '%s' (%i)", row[0], atoul(row[0]));
}
LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u emote state(s)", total);
total = 0;
result = query2.RunQuery2(Q_SELECT, "SELECT name, spell_visual_id, alternate_spell_visual, min_version_range, max_version_range FROM spell_visuals");
while(result && (row = mysql_fetch_row(result)))
{
EmoteVersionRange* range = 0;
if ((range = visual_states.FindSpellVisualRange(string(row[0]))) == NULL)
{
range = new EmoteVersionRange(row[0]);
visual_states.InsertSpellVisualRange(range, atoul(row[1]));
}
range->AddVersionRange(atoul(row[3]),atoul(row[4]), row[0], atoul(row[1]), row[2]);
total++;
LogWrite(WORLD__DEBUG, 5, "World", "---Loading spell visual state: '%s' (%u)", row[1], atoul(row[0]));
}
LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u spell visual state(s)", total);
}
void WorldDatabase::LoadSubCommandList()
@ -7632,7 +7650,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
multimap<LuaSpell*, Entity*> restoreSpells;
// Use -1 on type and subtype to turn the enum into an int and make it a 0 index
if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) {
if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, has_damaged, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
@ -7668,6 +7686,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
int8 last_spellattack_hit = result.GetInt32Str("last_spellattack_hit");
int8 interrupted = result.GetInt32Str("interrupted");
int8 resisted = result.GetInt32Str("resisted");
int8 has_damaged = result.GetInt32Str("has_damaged");
std::string custom_function = std::string(result.GetStringStr("custom_function"));
LuaSpell* lua_spell = 0;
if(custom_spell)
@ -7788,6 +7807,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
lua_spell->interrupted = interrupted;
lua_spell->last_spellattack_hit = last_spellattack_hit;
lua_spell->num_triggers = num_triggers;
lua_spell->has_damaged = has_damaged;
lua_spell->is_damage_spell = has_damaged;
}
if(lua_spell->initial_target == 0 && target_char_id == 0xFFFFFFFF && player->HasPet())

View file

@ -86,6 +86,7 @@ along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
#include "Tradeskills/Tradeskills.h"
#include "AltAdvancement/AltAdvancement.h"
#include "Bots/Bot.h"
#include "VisualStates.h"
extern WorldDatabase database;
extern const char* ZONE_NAME;
@ -119,6 +120,7 @@ extern MasterAAList master_aa_list;
extern MasterAAList master_tree_nodes;
extern ChestTrapList chest_trap_list;
extern MasterRecipeBookList master_recipebook_list;
extern VisualStates visual_states;
using namespace std;
@ -3036,7 +3038,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
sent_item_details[id] = true;
MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
//DumpPacket(app);
QueuePacket(app);
if (wasSpawn)
delete item;
@ -3070,7 +3072,6 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
sent_item_details[id] = true;
MItemDetails.releasewritelock(__FUNCTION__, __LINE__);
EQ2Packet* app = item->serialize(GetVersion(), false, GetPlayer());
//DumpPacket(app);
QueuePacket(app);
}
else {
@ -3099,7 +3100,7 @@ void Client::HandleExamineInfoRequest(EQApplicationPacket* app) {
if (item) {
//only display popup for non merchant links
EQ2Packet* app = item->serialize(GetVersion(), (request->getType_int8_ByName("show_popup") != 0), GetPlayer(), true, 0, 0, GetVersion() > 546 ? true : false);
//DumpPacket(app);
QueuePacket(app);
}
else {
@ -7965,9 +7966,7 @@ void Client::SendBuyMerchantList(bool sell) {
else
packet->setDataByName("type", 2);
}
packet->PrintPacket();
EQ2Packet* outapp = packet->serialize();
// DumpPacket(outapp);
QueuePacket(outapp);
safe_delete(packet);
}
@ -8541,7 +8540,6 @@ void Client::SendMailList() {
p->setArrayDataByName("player_to_id", mail->player_to_id, i);
p->setArrayDataByName("player_from", mail->player_from.c_str(), i);
p->setArrayDataByName("subject", mail->subject.c_str(), i);
p->setArrayDataByName("unknown1", 0x0000, i);
p->setArrayDataByName("already_read", mail->already_read, i);
if(mail->expire_time)
p->setArrayDataByName("mail_deletion", mail->expire_time - mail->time_sent, i);
@ -8598,6 +8596,8 @@ void Client::SendMailList() {
p->setDataByName("unknown3", 0x01F4);
p->setDataByName("unknown4", 0x01000000);
EQ2Packet* pack = p->serialize();
//DumpPacket(pack);
//p->PrintPacket();
QueuePacket(pack);
safe_delete(p);
}
@ -12415,4 +12415,45 @@ ZoneServer* Client::GetHouseZoneServer(int32 spawn_id, int64 house_id) {
}
return nullptr;
}
void Client::SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time) {
PacketStruct* packet = configReader.getStruct("WS_HearCastSpell", GetVersion());
if (packet) {
int32 caster_id = GetPlayer()->GetIDWithPlayerSpawn(caster);
int32 target_id = GetPlayer()->GetIDWithPlayerSpawn(target);
packet->setDataByName("spawn_id", caster_id);
packet->setArrayLengthByName("num_targets", 1);
packet->setArrayDataByName("target", target_id);
packet->setDataByName("num_targets", 1);
int32 visual = GetSpellVisualOverride(spell_visual);
packet->setDataByName("spell_visual", visual); //result
packet->setDataByName("cast_time", cast_time*.01f); //delay
packet->setDataByName("spell_id", 1);
packet->setDataByName("spell_level", 1);
packet->setDataByName("spell_tier", 1);
EQ2Packet* outapp = packet->serialize();
DumpPacket(outapp);
QueuePacket(outapp);
safe_delete(packet);
}
}
int32 Client::GetSpellVisualOverride(int32 spell_visual) {
int32 visual = spell_visual;
if(GetVersion() <= 546) { // spell's spell_visual field is based on newer clients, DoF has to convert
Emote* spellVisualEmote = visual_states.FindSpellVisualByID(visual, 60085);
if(spellVisualEmote != nullptr && spellVisualEmote->GetMessageString().size() > 0) {
spellVisualEmote = visual_states.FindSpellVisual(spellVisualEmote->GetMessageString(), GetVersion());
if(spellVisualEmote) {
visual = (int32)spellVisualEmote->GetVisualState();
}
}
}
return visual;
}

View file

@ -585,6 +585,9 @@ public:
void SendReplaceWidget(int32 widget_id, bool delete_widget, float x=0.0f, float y=0.0f, float z=0.0f, int32 grid_id=0);
void ProcessZoneIgnoreWidgets();
void SendHearCast(Spawn* caster, Spawn* target, int32 spell_visual, int16 cast_time);
int32 GetSpellVisualOverride(int32 spell_visual);
private:
void AddRecipeToPlayerPack(Recipe* recipe, PacketStruct* packet, int16* i);
void SavePlayerImages();

View file

@ -5249,13 +5249,18 @@ void ZoneServer::SendInterruptPacket(Spawn* interrupted, LuaSpell* spell, bool f
safe_delete(packet);
}
void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override, int16 casttime_override){
EQ2Packet* outapp = 0;
PacketStruct* packet = 0;
Client* client = 0;
if(!caster || !spell || !spell->spell || spell->interrupted)
return;
if(spell->is_damage_spell && (!spell->has_damaged || spell->resisted)) {
// we did not successfully hit target, so we should not send the visual
return;
}
vector<Client*>::iterator client_itr;
MClientList.readlock(__FUNCTION__, __LINE__);
@ -5284,8 +5289,17 @@ void ZoneServer::SendCastSpellPacket(LuaSpell* spell, Entity* caster){
packet->setArrayDataByName("target", 0xFFFFFFFF, i);
}
}
packet->setDataByName("spell_visual", spell->spell->GetSpellData()->spell_visual); //result
packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
int32 visual_to_use = spell_visual_override > 0 ? spell_visual_override : spell->spell->GetSpellData()->spell_visual;
int32 visual = client->GetSpellVisualOverride(visual_to_use);
packet->setDataByName("spell_visual", visual); //result
if(casttime_override != 0xFFFF) {
packet->setDataByName("cast_time", casttime_override*.01f); //delay
}
else {
packet->setDataByName("cast_time", spell->spell->GetSpellData()->cast_time*.01f); //delay
}
packet->setDataByName("spell_id", spell->spell->GetSpellID());
packet->setDataByName("spell_level", 1);
packet->setDataByName("spell_tier", spell->spell->GetSpellData()->tier);
@ -5330,7 +5344,10 @@ void ZoneServer::SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* c
}
packet->setArrayLengthByName("num_targets", 1);
packet->setArrayDataByName("target", target_id);
packet->setDataByName("spell_visual", spell_visual);
int32 visual = client->GetSpellVisualOverride(spell_visual);
packet->setDataByName("spell_visual", visual);
packet->setDataByName("cast_time", 0);
packet->setDataByName("spell_id", 0);
packet->setDataByName("spell_level", 0);

View file

@ -355,7 +355,7 @@ public:
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);
void SendCastSpellPacket(LuaSpell* spell, Entity* caster);
void SendCastSpellPacket(LuaSpell* spell, Entity* caster, int32 spell_visual_override = 0, int16 casttime_override = 0xFFFF);
void SendCastSpellPacket(int32 spell_visual, Spawn* target, Spawn* caster = 0);
void SendCastEntityCommandPacket(EntityCommand* entity_command, int32 spawn_id, int32 target_id);
void TriggerCharSheetTimer();

View file

@ -279,7 +279,7 @@ bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 len
if(valid)
return true;
}
else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff) {
else if(p->pBuffer[offset] != 0xff && p->pBuffer[offset+1] == 0xff && p->size > (1+offset)) {
uint8 new_length = 0;
memcpy(&new_length, p->pBuffer+offset, sizeof(int8));
@ -291,7 +291,7 @@ bool EQStream::HandleEmbeddedPacket(EQProtocolPacket *p, int16 offset, int16 len
delete subp;
return true;
}
}
}
}
return false;
}
@ -1467,8 +1467,9 @@ void EQStream::Write(int eq_fd)
//no more data to send
if (GetState() == CLOSING) {
MOutboundQueue.lock();
if (SequencedQueue.size() > 0 )
LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, client should be disconnecting, awaiting acknowledgement of SequencedQueue.");
if (SequencedQueue.size() > 0 ) {
// retransmission attempts
}
else
{
LogWrite(PACKET__DEBUG, 9, "Packet", "All outgoing data flushed, disconnecting client.");

View file

@ -1029,6 +1029,7 @@
<Data ElementName="index" Type="int16" Size="1" />
<Data ElementName="icon" Type="int16" Size="1" />
<Data ElementName="count" Type="int8" Size="1" />
<Data ElementName="unknown" Type="int8" Size="1" />
<Data ElementName="level" Type="int8" Size="1" />
<Data ElementName="tier" Type="int8" Size="1" />
<Data ElementName="num_slots" Type="int8" Size="1" />
@ -1036,19 +1037,20 @@
<Data ElementName="name" Type="char" Size="81" />
</Struct>
<Struct Name="Substruct_Item" ClientVersion="546" >
<Data ElementName="unique_id" Type="int32" Size="1" />
<Data ElementName="bag_id" Type="int32" Size="1" />
<Data ElementName="inv_slot_id" Type="int32" Size="1" />
<Data ElementName="menu_type" Type="int32" Size="1" />
<Data ElementName="slot_id" Type="int8" Size="1" />
<Data ElementName="index" Type="int16" Size="1" />
<Data ElementName="icon" Type="int16" Size="1" />
<Data ElementName="count" Type="int8" Size="1" />
<Data ElementName="level" Type="int8" Size="1" />
<Data ElementName="tier" Type="int8" Size="1" />
<Data ElementName="num_slots" Type="int8" Size="1" />
<Data ElementName="item_id" Type="sint32" Size="1" />
<Data ElementName="name" Type="char" Size="81" />
<Data ElementName="unique_id" Type="int32" Size="1" /><!-- 4 -->
<Data ElementName="bag_id" Type="int32" Size="1" /><!-- 8 -->
<Data ElementName="inv_slot_id" Type="int32" Size="1" /><!-- 12 -->
<Data ElementName="menu_type" Type="int32" Size="1" /><!-- 16 -->
<Data ElementName="slot_id" Type="int8" Size="1" /><!-- 17 -->
<Data ElementName="index" Type="int16" Size="1" /><!-- 19 -->
<Data ElementName="icon" Type="int16" Size="1" /> <!-- 21 -->
<Data ElementName="count" Type="int8" Size="1" /> <!-- 23 -->
<Data ElementName="level" Type="int8" Size="1" /> <!-- 24 -->
<Data ElementName="tier" Type="int8" Size="1" /> <!-- 25 -->
<Data ElementName="num_slots" Type="int8" Size="1" /> <!-- 26 -->
<Data ElementName="item_id" Type="sint32" Size="1" /> <!-- 27 -->
<Data ElementName="name" Type="char" Size="64" />
<Data ElementName="unknown6" Type="int8" Size="17" />
</Struct>
<Struct Name="Substruct_Item" ClientVersion="547" >
<Data ElementName="unique_id" Type="int32" Size="1" />

View file

@ -8,11 +8,12 @@
function cast(Caster, Target, DmgType, MinVal, MaxVal)
local damageInflicted = false;
-- Inflicts 12 - 20 piercing damage on target
if MaxVal ~= nil and MinVal < MaxVal then
SpellDamage(Target, DmgType, math.random(MinVal, MaxVal))
damageInflicted = SpellDamage(Target, DmgType, math.random(MinVal, MaxVal))
else
SpellDamage(Target, DmgType, MinVal)
damageInflicted = SpellDamage(Target, DmgType, MinVal)
end
-- Applies Knockdown on termination. Lasts for 1.5 seconds.
@ -20,8 +21,7 @@ function cast(Caster, Target, DmgType, MinVal, MaxVal)
-- Stuns target
-- Blurs vision of target
-- Does not affect Epic targets
if not IsEpic(Target) then
Knockback(Caster, Target, 1500)
if damageInflicted and not IsEpic(Target) then
AddControlEffect(Target, 4)
BlurVision(Target, 1.0)
AddSpellTimer(1500, "RemoveStunBlur")

View file

@ -2938,6 +2938,21 @@ to zero and treated like placeholders." />
<Data ElementName="spell" Type="int8" />
<Data ElementName="spell_name" Type="EQ2_16Bit_String" Size="1" />
</Struct>
<Struct Name="WS_HearSiphonSpellDamage" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearCombatCmd">
<Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
<Data ElementName="num_dmg" Type="int8" />
<Data ElementName="siphon_type" Type="int8" />
<Data ElementName="dmg_array" Type="Array" ArraySizeVariable="num_dmg">
<Data ElementName="damage_type" Type="int8" />
<Data ElementName="damage" Type="int16" />
<Data ElementName="unknown1" Type="int8" />
<Data ElementName="unknown2" Type="int8" />
<Data ElementName="crit_flag" Type="int8" /> <!-- 4==crit -->
<Data ElementName="unknown4" Type="int8" />
</Data>
<Data ElementName="spell" Type="int8" />
<Data ElementName="spell_name" Type="EQ2_16Bit_String" Size="1" />
</Struct>
<Struct Name="WS_HearSiphonSpellDamage" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearCombatCmd">
<Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
<Data ElementName="siphon_type" Type="int8" />
@ -3094,8 +3109,8 @@ to zero and treated like placeholders." />
<Data ElementName="header" Substruct="WS_HearDamage_Header" Size="1" />
<Data ElementName="siphon_type" Type="int8" />
<Data ElementName="siphon_subtype" Type="int8" />
<Data ElementName="damage" Type="int16" />
<Data ElementName="unknown1" Type="int8" Size="2" />
<Data ElementName="damage" Type="int32" />
<Data ElementName="unknown1" Type="int8" Size="5" />
<Data ElementName="spell_name" Type="EQ2_8Bit_String" Size="1" />
<!-- All Hear spell damages so far seem to have new bytes at the end (who knows for how long) -->
<Data ElementName="unknown2" Type="int8" Size="5" />
@ -3132,12 +3147,20 @@ to zero and treated like placeholders." />
<!-- All Hear spell damages so far seem to have new bytes at the end (who knows for how long) -->
<Data ElementName="unknown2" Type="int8" Size="5" />
</Struct>
<!-- WS_HearHeal may be innaccurate, copied from DoF -->
<Struct Name="WS_HearHeal" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
<Data ElementName="caster" Type="int32" />
<Data ElementName="target" Type="int32" />
<Data ElementName="heal_amt" Type="int32" />
<Data ElementName="heal_amt" Type="int16" />
<Data ElementName="spellname" Type="EQ2_16Bit_String" Size="1"/>
<Data ElementName="type" Type="int16" />
<Data ElementName="type" Type="int8" />
</Struct>
<Struct Name="WS_HearHeal" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
<Data ElementName="caster" Type="int32" />
<Data ElementName="target" Type="int32" />
<Data ElementName="heal_amt" Type="int16" />
<Data ElementName="spellname" Type="EQ2_16Bit_String" Size="1"/>
<Data ElementName="type" Type="int8" />
</Struct>
<Struct Name="WS_HearHeal" ClientVersion="57048" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqHearHealCmd">
<Data ElementName="caster" Type="int32" />