Merge from master

This commit is contained in:
KimLS 2013-05-08 20:22:40 -07:00
commit feae79b417
47 changed files with 1222 additions and 142 deletions

View File

@ -1,5 +1,15 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) EQEMu Changelog (Started on Sept 24, 2003 15:50)
------------------------------------------------------- -------------------------------------------------------
== 05/05/2013 ==
Tabasco: Added EVENT_ITEM_TICK for interactive item quest scripts. (See https://github.com/josheb/quests_dc/blob/master/items/12204.pl)
Added simple account flags. Would this work better as an additional qglobal category?
Added SendMessage to EQW, useful for using CURL to broadcast to the server.
Added WorldShutDown to EQW for timed world shutdowns, also updated console worldshutdown command to optionally use timed behavior.
Added numerous modding hooks and mod_functions.cpp for easy custom formulas or future lua integration.
Added various rules for monk AC bonus weight, archery bonuses, kick/bash stuns and mob stun levels.
required SQL: 2013_05_05_Account_Flags.sql
required SQL: 2013_05_05_Item_Tick.sql
== 04/28/2013 == == 04/28/2013 ==
PiB: Implement CRC16 using CRC32. PiB: Implement CRC16 using CRC32.

View File

@ -428,7 +428,7 @@ bool Database::ReserveName(uint32 account_id, char* name)
char errbuf[MYSQL_ERRMSG_SIZE]; char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0; char *query = 0;
if (!RunQuery(query, MakeAnyLenString(&query, "INSERT into character_ SET account_id=%i, name='%s', profile=nullptr", account_id, name), errbuf)) { if (!RunQuery(query, MakeAnyLenString(&query, "INSERT into character_ SET account_id=%i, name='%s', profile=NULL", account_id, name), errbuf)) {
cerr << "Error in ReserveName query '" << query << "' " << errbuf << endl; cerr << "Error in ReserveName query '" << query << "' " << errbuf << endl;
safe_delete_array(query); safe_delete_array(query);
return false; return false;
@ -1196,9 +1196,9 @@ bool Database::GetSafePoints(const char* short_name, uint32 version, float* safe
{ {
cerr << "Error in GetSafePoint query '" << query << "' " << errbuf << endl; cerr << "Error in GetSafePoint query '" << query << "' " << errbuf << endl;
cerr << "If it errors, run the following querys:\n"; cerr << "If it errors, run the following querys:\n";
cerr << "ALTER TABLE `zone` CHANGE `minium_level` `min_level` TINYINT(3) UNSIGNED DEFAULT \"0\" NOT nullptr;\n"; cerr << "ALTER TABLE `zone` CHANGE `minium_level` `min_level` TINYINT(3) UNSIGNED DEFAULT \"0\" NOT NULL;\n";
cerr << "ALTER TABLE `zone` CHANGE `minium_status` `min_status` TINYINT(3) UNSIGNED DEFAULT \"0\" NOT nullptr;\n"; cerr << "ALTER TABLE `zone` CHANGE `minium_status` `min_status` TINYINT(3) UNSIGNED DEFAULT \"0\" NOT NULL;\n";
cerr << "ALTER TABLE `zone` ADD flag_needed VARCHAR(128) NOT nullptr DEFAULT '';\n"; cerr << "ALTER TABLE `zone` ADD flag_needed VARCHAR(128) NOT NULL DEFAULT '';\n";
safe_delete_array(query); safe_delete_array(query);
} }

View File

@ -95,7 +95,7 @@ RULE_BOOL ( Character, CheckCursorEmptyWhenLooting, true ) // If true, a player
RULE_BOOL ( Character, MaintainIntoxicationAcrossZones, true ) // If true, alcohol effects are maintained across zoning and logging out/in. RULE_BOOL ( Character, MaintainIntoxicationAcrossZones, true ) // If true, alcohol effects are maintained across zoning and logging out/in.
RULE_BOOL ( Character, EnableDiscoveredItems, true ) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered. RULE_BOOL ( Character, EnableDiscoveredItems, true ) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered.
RULE_BOOL ( Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. RULE_BOOL ( Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients.
RULE_BOOL ( Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY( Mercs ) RULE_CATEGORY( Mercs )
@ -292,6 +292,7 @@ RULE_INT ( Spells, RootBreakFromSpells, 20) //Chance for root to break when cast
RULE_INT ( Spells, DeathSaveCharismaMod, 3) //Determines how much charisma effects chance of death save firing. RULE_INT ( Spells, DeathSaveCharismaMod, 3) //Determines how much charisma effects chance of death save firing.
RULE_INT ( Spells, DivineInterventionHeal, 8000) //Divine intervention heal amount. RULE_INT ( Spells, DivineInterventionHeal, 8000) //Divine intervention heal amount.
RULE_BOOL ( Spells, AdditiveBonusValues, false) //Allow certain bonuses to be calculated by adding together the value from each item, instead of taking the highest value. (ie Add together all Cleave Effects) RULE_BOOL ( Spells, AdditiveBonusValues, false) //Allow certain bonuses to be calculated by adding together the value from each item, instead of taking the highest value. (ie Add together all Cleave Effects)
RULE_BOOL ( Spells, UseCHAScribeHack, false) //ScribeSpells and TrainDiscs quest functions will ignore entries where field 12 is CHA. What's the best way to do this?
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY( Combat ) RULE_CATEGORY( Combat )
@ -361,6 +362,11 @@ RULE_INT ( Combat, NPCFlurryChance, 20) // Chance for NPC to flurry.
RULE_BOOL (Combat,TauntOverLevel, 1) //Allows you to taunt NPC's over warriors level. RULE_BOOL (Combat,TauntOverLevel, 1) //Allows you to taunt NPC's over warriors level.
RULE_REAL (Combat,TauntSkillFalloff, 0.33)//For every taunt skill point that's not maxed you lose this % chance to taunt. RULE_REAL (Combat,TauntSkillFalloff, 0.33)//For every taunt skill point that's not maxed you lose this % chance to taunt.
RULE_BOOL (Combat,EXPFromDmgShield, false) //Determine if damage from a damage shield counts for EXP gain. RULE_BOOL (Combat,EXPFromDmgShield, false) //Determine if damage from a damage shield counts for EXP gain.
RULE_INT ( Combat, MonkACBonusWeight, 15)
RULE_INT ( Combat, ClientStunLevel, 55) //This is the level where client kicks and bashes can stun the target
RULE_INT ( Combat, QuiverWRHasteDiv, 3) //Weight Reduction is divided by this to get haste contribution for quivers
RULE_BOOL ( Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus require an actual roll
RULE_INT ( Combat, ArcheryBonusChance, 50)
RULE_CATEGORY_END() RULE_CATEGORY_END()
RULE_CATEGORY( NPC ) RULE_CATEGORY( NPC )

View File

@ -1102,3 +1102,7 @@ bool IsShortDurationBuff(uint16 spell_id)
return false; return false;
} }
const char* GetSpellName(int16 spell_id)
{
return( spells[spell_id].name );
}

View File

@ -806,5 +806,6 @@ bool DetrimentalSpellAllowsRest(uint16 spell_id);
uint32 GetNimbusEffect(uint16 spell_id); uint32 GetNimbusEffect(uint16 spell_id);
int32 GetFuriousBash(uint16 spell_id); int32 GetFuriousBash(uint16 spell_id);
bool IsShortDurationBuff(uint16 spell_id); bool IsShortDurationBuff(uint16 spell_id);
const char *GetSpellName(int16 spell_id);
#endif #endif

View File

@ -0,0 +1,11 @@
--
-- Table structure for table `account_flags`
--
CREATE TABLE IF NOT EXISTS `account_flags` (
`p_accid` int(10) unsigned NOT NULL,
`p_flag` varchar(50) NOT NULL,
`p_value` varchar(80) NOT NULL,
PRIMARY KEY (`p_accid`,`p_flag`),
KEY `p_accid` (`p_accid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

View File

@ -0,0 +1,13 @@
--
-- Table structure for table `item_tick`
--
CREATE TABLE IF NOT EXISTS `item_tick` (
`it_itemid` int(11) NOT NULL,
`it_chance` int(11) NOT NULL,
`it_level` int(11) NOT NULL,
`it_id` int(11) NOT NULL AUTO_INCREMENT,
`it_qglobal` varchar(50) NOT NULL,
`it_bagslot` tinyint(4) NOT NULL,
PRIMARY KEY (`it_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

View File

@ -459,6 +459,13 @@ void EQW::ResolveBug(const char *id) {
safe_delete_array(query); safe_delete_array(query);
} }
void EQW::SendMessage(uint32 type, const char *msg) {
zoneserver_list.SendEmoteMessage(0, 0, 0, type, msg);
}
void EQW::WorldShutDown(uint32 time, uint32 interval) {
zoneserver_list.WorldShutDown(time, interval);
}
#endif //EMBPERL #endif //EMBPERL

View File

@ -78,6 +78,8 @@ public:
map<string,string> GetBugDetails(const char *id); map<string,string> GetBugDetails(const char *id);
void ResolveBug(const char *id); void ResolveBug(const char *id);
void SendMessage(uint32 type, const char *msg);
void WorldShutDown(uint32 time, uint32 interval);
//END PERL EXPORT //END PERL EXPORT
protected: protected:

View File

@ -722,11 +722,24 @@ void Console::ProcessCommand(const char* command) {
} }
} }
else if (strcasecmp(sep.arg[0], "worldshutdown") == 0 && admin >= consoleWorldStatus) { else if (strcasecmp(sep.arg[0], "worldshutdown") == 0 && admin >= consoleWorldStatus) {
ServerPacket* pack = new ServerPacket(ServerOP_ShutdownAll); int32 time, interval;
zoneserver_list.SendPacket(pack); if(sep.IsNumber(1) && sep.IsNumber(2) && ((time=atoi(sep.arg[1]))>0) && ((interval=atoi(sep.arg[2]))>0)) {
delete pack; zoneserver_list.WorldShutDown(time, interval);
SendMessage(1, "Sending shutdown packet... goodbye."); }
CatchSignal(0); else if(strcasecmp(sep.arg[1], "now") == 0) {
zoneserver_list.WorldShutDown(0, 0);
}
else if(strcasecmp(sep.arg[1], "disable") == 0) {
SendEmoteMessage(0,0,0,15,"<SYSTEMWIDE MESSAGE>:SYSTEM MSG:World shutdown aborted.");
zoneserver_list.SendEmoteMessage(0,0,0,15,"<SYSTEMWIDE MESSAGE>:SYSTEM MSG:World shutdown aborted.");
zoneserver_list.shutdowntimer->Disable();
zoneserver_list.reminder->Disable();
}
else {
SendMessage(1, "Usage: worldshutdown [now] [disable] ([time] [interval])");
//Go ahead and shut down since that's what this used to do when invoked this way.
zoneserver_list.WorldShutDown(0, 0);
}
} }
else if (strcasecmp(sep.arg[0], "lock") == 0 && admin >= consoleLockStatus) { else if (strcasecmp(sep.arg[0], "lock") == 0 && admin >= consoleLockStatus) {
WorldConfig::LockWorld(); WorldConfig::LockWorld();

View File

@ -969,6 +969,57 @@ XS(XS_EQW_ResolveBug)
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
XS(XS_EQW_SendMessage); /* prototype to pass -Wmissing-prototypes */
XS(XS_EQW_SendMessage)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: EQW::SendMessage(THIS, type, message)");
{
EQW * THIS;
dXSTARG;
uint32 msgtype = (uint32)SvUV(ST(1));
char* msg = (char *)SvPV_nolen(ST(2));
if (sv_derived_from(ST(0), "EQW")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(EQW *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type EQW");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->SendMessage(msgtype, msg);
}
XSRETURN_EMPTY;
}
XS(XS_EQW_WorldShutDown); /* prototype to pass -Wmissing-prototypes */
XS(XS_EQW_WorldShutDown)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: EQW::WorldShutDown(THIS, time, interval)");
{
EQW * THIS;
dXSTARG;
uint32 time = (uint32)SvUV(ST(1));
uint32 interval = (uint32)SvUV(ST(2));
if (sv_derived_from(ST(0), "EQW")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(EQW *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type EQW");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->WorldShutDown(time, interval);
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@ -1020,6 +1071,8 @@ XS(boot_EQW)
newXSproto(strcpy(buf, "ListBugs"), XS_EQW_ListBugs, file, "$$"); newXSproto(strcpy(buf, "ListBugs"), XS_EQW_ListBugs, file, "$$");
newXSproto(strcpy(buf, "GetBugDetails"), XS_EQW_GetBugDetails, file, "$$"); newXSproto(strcpy(buf, "GetBugDetails"), XS_EQW_GetBugDetails, file, "$$");
newXSproto(strcpy(buf, "ResolveBug"), XS_EQW_ResolveBug, file, "$$"); newXSproto(strcpy(buf, "ResolveBug"), XS_EQW_ResolveBug, file, "$$");
newXSproto(strcpy(buf, "SendMessage"), XS_EQW_SendMessage, file, "$$$");
newXSproto(strcpy(buf, "WorldShutDown"), XS_EQW_WorldShutDown, file, "$$$");
XSRETURN_YES; XSRETURN_YES;
} }

View File

@ -716,14 +716,29 @@ void ZSList::GetZoneIDList(vector<uint32> &zones) {
} }
void ZSList::WorldShutDown(uint32 time, uint32 interval)
{
if( time > 0 ) {
SendEmoteMessage(0,0,0,15,"<SYSTEMWIDE MESSAGE>:SYSTEM MSG:World coming down in %i seconds, everyone log out before this time.",time);
time *= 1000;
interval *= 1000;
if(interval < 5000) { interval = 5000; }
shutdowntimer->SetTimer(time);
reminder->SetTimer(interval-1000);
reminder->SetAtTrigger(interval);
shutdowntimer->Start();
reminder->Start();
}
else {
SendEmoteMessage(0,0,0,15,"<SYSTEMWIDE MESSAGE>:SYSTEM MSG:World coming down, everyone log out now.");
ServerPacket* pack = new ServerPacket;
pack->opcode = ServerOP_ShutdownAll;
pack->size=0;
SendPacket(pack);
safe_delete(pack);
Process();
CatchSignal(2);
}
}

View File

@ -56,6 +56,7 @@ public:
int GetZoneCount(); int GetZoneCount();
void GetZoneIDList(std::vector<uint32> &zones); void GetZoneIDList(std::vector<uint32> &zones);
void WorldShutDown(uint32 time, uint32 interval);
protected: protected:
uint32 NextID; uint32 NextID;

View File

@ -85,6 +85,7 @@ SET(zone_sources
zonedb.cpp zonedb.cpp
zonedbasync.cpp zonedbasync.cpp
zoning.cpp zoning.cpp
mod_functions.cpp
) )
SET(zone_headers SET(zone_headers

View File

@ -318,6 +318,8 @@ bool Mob::CheckHitChance(Mob* other, SkillType skillinuse, int Hand, int16 chanc
if(skillinuse == ARCHERY) if(skillinuse == ARCHERY)
chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f; chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f;
chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker);
// Chance to hit; Max 95%, Min 30% // Chance to hit; Max 95%, Min 30%
if(chancetohit > 1000) { if(chancetohit > 1000) {
//if chance to hit is crazy high, that means a discipline is in use, and let it stay there //if chance to hit is crazy high, that means a discipline is in use, and let it stay there
@ -396,6 +398,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte)
if (!ghit) { //if they are not using a garunteed hit discipline if (!ghit) { //if they are not using a garunteed hit discipline
bonus = 2.0 + skill/60.0 + (GetDEX()/200); bonus = 2.0 + skill/60.0 + (GetDEX()/200);
bonus *= riposte_chance; bonus *= riposte_chance;
bonus = mod_riposte_chance(bonus, attacker);
RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block
} }
} }
@ -434,6 +437,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte)
if (!ghit) { //if they are not using a garunteed hit discipline if (!ghit) { //if they are not using a garunteed hit discipline
bonus = 2.0 + skill/35.0 + (GetDEX()/200); bonus = 2.0 + skill/35.0 + (GetDEX()/200);
bonus = mod_block_chance(bonus, attacker);
RollTable[1] = RollTable[0] + (bonus * block_chance); RollTable[1] = RollTable[0] + (bonus * block_chance);
} }
} }
@ -485,6 +489,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte)
if (!ghit) { //if they are not using a garunteed hit discipline if (!ghit) { //if they are not using a garunteed hit discipline
bonus = 2.0 + skill/60.0 + (GetDEX()/200); bonus = 2.0 + skill/60.0 + (GetDEX()/200);
bonus *= parry_chance; bonus *= parry_chance;
bonus = mod_parry_chance(bonus, attacker);
RollTable[2] = RollTable[1] + bonus; RollTable[2] = RollTable[1] + bonus;
} }
} }
@ -507,6 +512,8 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte)
if (!ghit) { //if they are not using a garunteed hit discipline if (!ghit) { //if they are not using a garunteed hit discipline
bonus = 2.0 + skill/60.0 + (GetAGI()/200); bonus = 2.0 + skill/60.0 + (GetAGI()/200);
bonus *= dodge_chance; bonus *= dodge_chance;
//DCBOOMKAR
bonus = mod_dodge_chance(bonus, attacker);
RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25); RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25);
} }
} }
@ -555,6 +562,10 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
int shield_ac = 0; int shield_ac = 0;
int armor = 0; int armor = 0;
float weight = 0.0; float weight = 0.0;
float monkweight = RuleI(Combat, MonkACBonusWeight);
monkweight = mod_monk_weight(monkweight, attacker);
if(IsClient()) if(IsClient())
{ {
armor = CastToClient()->GetRawACNoShield(shield_ac); armor = CastToClient()->GetRawACNoShield(shield_ac);
@ -576,7 +587,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
{ {
softcap = RuleI(Combat, ClothACSoftcap); softcap = RuleI(Combat, ClothACSoftcap);
} }
else if(GetClass() == MONK && weight <= 15.0) else if(GetClass() == MONK && weight <= monkweight)
{ {
softcap = RuleI(Combat, MonkACSoftcap); softcap = RuleI(Combat, MonkACSoftcap);
} }
@ -603,7 +614,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
{ {
softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn);
} }
else if(GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || (GetClass() == MONK && weight <= 15.0)) else if(GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || (GetClass() == MONK && weight <= monkweight))
{ {
softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn);
} }
@ -637,6 +648,8 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
} }
mitigation_rating *= 0.847; mitigation_rating *= 0.847;
mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker);
if(attacker->IsClient()) if(attacker->IsClient())
{ {
attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(OFFENSE)*1.345)); attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(OFFENSE)*1.345));
@ -646,6 +659,8 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
attack_rating = (attacker->GetATK() + (attacker->GetSkill(OFFENSE)*1.345) + ((attacker->GetSTR()-66) * 0.9)); attack_rating = (attacker->GetATK() + (attacker->GetSkill(OFFENSE)*1.345) + ((attacker->GetSTR()-66) * 0.9));
} }
attack_rating = attacker->mod_attack_rating(attack_rating, this);
float d = 10.0; float d = 10.0;
float mit_roll = MakeRandomFloat(0, mitigation_rating); float mit_roll = MakeRandomFloat(0, mitigation_rating);
float atk_roll = MakeRandomFloat(0, attack_rating); float atk_roll = MakeRandomFloat(0, attack_rating);
@ -669,6 +684,9 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
if(thac20 > thac20cap) if(thac20 > thac20cap)
{ {
thac20 = thac20cap; thac20 = thac20cap;
} }
d += 10 * (m_diff / thac20); d += 10 * (m_diff / thac20);
} }
@ -1221,6 +1239,8 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
else else
damage = MakeRandomInt(min_hit, max_hit); damage = MakeRandomInt(min_hit, max_hit);
damage = mod_client_damage(damage, skillinuse, Hand, weapon, other);
mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)",
damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel);
@ -1458,6 +1478,9 @@ void Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_
{ {
if (killerMob->IsNPC()) { if (killerMob->IsNPC()) {
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0); parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0);
mod_client_death_npc(killerMob);
uint16 emoteid = killerMob->GetEmoteID(); uint16 emoteid = killerMob->GetEmoteID();
if(emoteid != 0) if(emoteid != 0)
killerMob->CastToNPC()->DoNPCEmote(KILLEDPC,emoteid); killerMob->CastToNPC()->DoNPCEmote(KILLEDPC,emoteid);
@ -1473,6 +1496,9 @@ void Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_
killerMob->CastToClient()->SetDueling(false); killerMob->CastToClient()->SetDueling(false);
killerMob->CastToClient()->SetDuelTarget(0); killerMob->CastToClient()->SetDuelTarget(0);
entity_list.DuelMessage(killerMob,this,false); entity_list.DuelMessage(killerMob,this,false);
mod_client_death_duel(killerMob);
} else { } else {
//otherwise, we just died, end the duel. //otherwise, we just died, end the duel.
Mob* who = entity_list.GetMob(GetDuelTarget()); Mob* who = entity_list.GetMob(GetDuelTarget());
@ -1845,6 +1871,8 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
damage = (max_dmg+eleBane); damage = (max_dmg+eleBane);
} }
damage = mod_npc_damage(damage, skillinuse, Hand, &weapon_inst, other);
int32 hate = damage; int32 hate = damage;
if(IsPet()) if(IsPet())
{ {
@ -2043,7 +2071,7 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
entity_list.QueueClients(killerMob, app, false); entity_list.QueueClients(killerMob, app, false);
if(respawn2) { if(respawn2) {
respawn2->DeathReset(); respawn2->DeathReset(1);
} }
if (killerMob) { if (killerMob) {
@ -2098,6 +2126,9 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
for (int i = 0; i < MAX_RAID_MEMBERS; i++) { for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
if (kr->members[i].member != nullptr) { // If Group Member is Client if (kr->members[i].member != nullptr) { // If Group Member is Client
parse->EventNPC(EVENT_KILLED_MERIT, this, kr->members[i].member, "killed", 0); parse->EventNPC(EVENT_KILLED_MERIT, this, kr->members[i].member, "killed", 0);
mod_npc_killed_merit(kr->members[i].member);
if(RuleB(TaskSystem, EnableTaskSystem)) if(RuleB(TaskSystem, EnableTaskSystem))
kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID()); kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID());
PlayerCount++; PlayerCount++;
@ -2138,6 +2169,9 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client if (kg->members[i] != nullptr && kg->members[i]->IsClient()) { // If Group Member is Client
Client *c = kg->members[i]->CastToClient(); Client *c = kg->members[i]->CastToClient();
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0); parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
mod_npc_killed_merit(c);
if(RuleB(TaskSystem, EnableTaskSystem)) if(RuleB(TaskSystem, EnableTaskSystem))
c->UpdateTasksOnKill(GetNPCTypeID()); c->UpdateTasksOnKill(GetNPCTypeID());
@ -2182,6 +2216,9 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
} }
/* Send the EVENT_KILLED_MERIT event */ /* Send the EVENT_KILLED_MERIT event */
parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0); parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0);
mod_npc_killed_merit(give_exp_client);
if(RuleB(TaskSystem, EnableTaskSystem)) if(RuleB(TaskSystem, EnableTaskSystem))
give_exp_client->UpdateTasksOnKill(GetNPCTypeID()); give_exp_client->UpdateTasksOnKill(GetNPCTypeID());
@ -2312,6 +2349,9 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
if(killerMob) { if(killerMob) {
Mob *oos = killerMob->GetOwnerOrSelf(); Mob *oos = killerMob->GetOwnerOrSelf();
parse->EventNPC(EVENT_DEATH, this, oos, "", 0); parse->EventNPC(EVENT_DEATH, this, oos, "", 0);
mod_npc_killed(oos);
uint16 emoteid = this->GetEmoteID(); uint16 emoteid = this->GetEmoteID();
if(emoteid != 0) if(emoteid != 0)
this->DoNPCEmote(ONDEATH,emoteid); this->DoNPCEmote(ONDEATH,emoteid);
@ -3404,7 +3444,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons
{ {
// NPCs can stun with their bash/kick as soon as they recieve it. // NPCs can stun with their bash/kick as soon as they recieve it.
// Clients can stun mobs under level 56 with their bash/kick when they get level 55 or greater. // Clients can stun mobs under level 56 with their bash/kick when they get level 55 or greater.
if((attacker->IsNPC()) || (attacker->IsClient() && attacker->GetLevel() >= 55 && GetLevel() < 56)) if( attacker->IsNPC() || (attacker->IsClient() && attacker->GetLevel() >= RuleI(Combat, ClientStunLevel) && GetLevel() < RuleI(Spells, BaseImmunityLevel)) )
{ {
if (MakeRandomInt(0,99) < (RuleI(Character, NPCBashKickStunChance)) || attacker->IsClient()) if (MakeRandomInt(0,99) < (RuleI(Character, NPCBashKickStunChance)) || attacker->IsClient())
{ {

View File

@ -166,7 +166,8 @@ Client::Client(EQStreamInterface* ieqs)
qglobal_purge_timer(30000), qglobal_purge_timer(30000),
TrackingTimer(2000), TrackingTimer(2000),
RespawnFromHoverTimer(0), RespawnFromHoverTimer(0),
merc_timer(RuleI(Mercs, UpkeepIntervalMS)) merc_timer(RuleI(Mercs, UpkeepIntervalMS)),
ItemTickTimer(10000)
{ {
for(int cf=0; cf < _FilterCount; cf++) for(int cf=0; cf < _FilterCount; cf++)
ClientFilters[cf] = FilterShow; ClientFilters[cf] = FilterShow;
@ -325,6 +326,7 @@ Client::Client(EQStreamInterface* ieqs)
} }
MaxXTargets = 5; MaxXTargets = 5;
XTargetAutoAddHaters = true; XTargetAutoAddHaters = true;
LoadAccountFlags();
} }
Client::~Client() { Client::~Client() {
@ -842,6 +844,9 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
safe_delete(pack); safe_delete(pack);
} }
//Return true to proceed, false to return
if(!mod_client_message(message, chan_num)) { return; }
// Garble the message based on drunkness // Garble the message based on drunkness
if (m_pp.intoxication > 0) { if (m_pp.intoxication > 0) {
GarbleMessage(message, (int)(m_pp.intoxication / 3)); GarbleMessage(message, (int)(m_pp.intoxication / 3));
@ -2337,7 +2342,8 @@ bool Client::CheckIncreaseSkill(SkillType skillid, Mob *against_who, int chancem
if( against_who->SpecAttacks[IMMUNE_AGGRO] || against_who->IsClient() || if( against_who->SpecAttacks[IMMUNE_AGGRO] || against_who->IsClient() ||
GetLevelCon(against_who->GetLevel()) == CON_GREEN ) GetLevelCon(against_who->GetLevel()) == CON_GREEN )
{ {
return false; //false by default
return mod_can_increase_skill(skillid, against_who);
} }
} }
@ -2349,6 +2355,9 @@ bool Client::CheckIncreaseSkill(SkillType skillid, Mob *against_who, int chancem
if (Chance < 1) if (Chance < 1)
Chance = 1; // Make it always possible Chance = 1; // Make it always possible
Chance = (Chance * RuleI(Character, SkillUpModifier) / 100); Chance = (Chance * RuleI(Character, SkillUpModifier) / 100);
Chance = mod_increase_skill_chance(Chance, against_who);
if(MakeRandomFloat(0, 99) < Chance) if(MakeRandomFloat(0, 99) < Chance)
{ {
SetSkill(skillid, GetRawSkill(skillid) + 1); SetSkill(skillid, GetRawSkill(skillid) + 1);
@ -2717,6 +2726,8 @@ bool Client::BindWound(Mob* bindmob, bool start, bool fail){
max_percent = 70 + 10 * maxHPBonus; max_percent = 70 + 10 * maxHPBonus;
} }
max_percent = mod_bindwound_percent(max_percent, bindmob);
int max_hp = bindmob->GetMaxHP()*max_percent/100; int max_hp = bindmob->GetMaxHP()*max_percent/100;
// send bindmob new hp's // send bindmob new hp's
@ -2736,6 +2747,8 @@ bool Client::BindWound(Mob* bindmob, bool start, bool fail){
bindhps += bindhps*bindBonus / 100; bindhps += bindhps*bindBonus / 100;
bindhps = mod_bindwound_hp(bindhps, bindmob);
//if the bind takes them above the max bindable //if the bind takes them above the max bindable
//cap it at that value. Dont know if live does it this way //cap it at that value. Dont know if live does it this way
//but it makes sense to me. //but it makes sense to me.
@ -7622,3 +7635,110 @@ some day.
} }
return 0; return 0;
} }
void Client::LoadAccountFlags()
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
accountflags.clear();
MakeAnyLenString(&query, "SELECT p_flag, p_value FROM account_flags WHERE p_accid = '%d'", account_id);
if(database.RunQuery(query, strlen(query), errbuf, &result))
{
while(row = mysql_fetch_row(result))
{
std::string fname(row[0]);
std::string fval(row[1]);
accountflags[fname] = fval;
}
mysql_free_result(result);
}
else
{
std::cerr << "Error in LoadAccountFlags query '" << query << "' " << errbuf << std::endl;
}
safe_delete_array(query);
}
void Client::SetAccountFlag(std::string flag, std::string val)
{
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MakeAnyLenString(&query, "REPLACE INTO account_flags (p_accid, p_flag, p_value) VALUES( '%d', '%s', '%s')", account_id, flag.c_str(), val.c_str());
if(!database.RunQuery(query, strlen(query), errbuf))
{
std::cerr << "Error in SetAccountFlags query '" << query << "' " << errbuf << std::endl;
}
safe_delete_array(query);
accountflags[flag] = val;
}
std::string Client::GetAccountFlag(std::string flag)
{
return(accountflags[flag]);
}
void Client::TickItemCheck()
{
int i;
if(zone->tick_items.empty()) { return; }
//Scan equip slots for items
for(i = 0; i <= 21; i++)
{
TryItemTick(i);
}
//Scan main inventory + cursor
for(i = 22; i < 31; i++)
{
TryItemTick(i);
}
//Scan bags
for(i = 251; i < 340; i++)
{
TryItemTick(i);
}
}
void Client::TryItemTick(int slot)
{
int iid = 0;
const ItemInst* inst = m_inv[slot];
if(inst == 0) { return; }
iid = inst->GetID();
if(zone->tick_items.count(iid) > 0)
{
if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) && (zone->tick_items[iid].bagslot || slot < 22) )
{
ItemInst* e_inst = (ItemInst*)inst;
parse->EventItem(EVENT_ITEM_TICK, this, e_inst, e_inst->GetID(), slot);
}
}
//Only look at augs in main inventory
if(slot > 21) { return; }
for(int x = 0; x < MAX_AUGMENT_SLOTS; ++x)
{
ItemInst * a_inst = inst->GetAugment(x);
if(!a_inst) { continue; }
iid = a_inst->GetID();
if(zone->tick_items.count(iid) > 0)
{
if( GetLevel() >= zone->tick_items[iid].level && MakeRandomInt(0, 100) >= (100 - zone->tick_items[iid].chance) )
{
ItemInst* e_inst = (ItemInst*)a_inst;
parse->EventItem(EVENT_ITEM_TICK, this, e_inst, e_inst->GetID(), slot);
}
}
}
}

View File

@ -1134,6 +1134,35 @@ public:
void DuplicateLoreMessage(uint32 ItemID); void DuplicateLoreMessage(uint32 ItemID);
void GarbleMessage(char *, uint8); void GarbleMessage(char *, uint8);
void TickItemCheck();
void TryItemTick(int slot);
int16 GetActSTR() { return( min(GetMaxSTR(), GetSTR()) ); }
int16 GetActSTA() { return( min(GetMaxSTA(), GetSTA()) ); }
int16 GetActDEX() { return( min(GetMaxDEX(), GetDEX()) ); }
int16 GetActAGI() { return( min(GetMaxAGI(), GetAGI()) ); }
int16 GetActINT() { return( min(GetMaxINT(), GetINT()) ); }
int16 GetActWIS() { return( min(GetMaxWIS(), GetWIS()) ); }
int16 GetActCHA() { return( min(GetMaxCHA(), GetCHA()) ); }
void LoadAccountFlags();
void SetAccountFlag(std::string flag, std::string val);
std::string GetAccountFlag(std::string flag); float GetDamageMultiplier(SkillType);
int mod_client_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other);
bool mod_client_message(char* message, uint8 chan_num);
bool mod_can_increase_skill(SkillType skillid, Mob* against_who);
int16 mod_increase_skill_chance(int16 chance, Mob* against_who);
int mod_bindwound_percent(int max_percent, Mob* bindmob);
int mod_bindwound_hp(int bindhps, Mob* bindmob);
int mod_client_haste(int h);
void mod_consider(Mob* tmob, Consider_Struct* con);
bool mod_saylink(const std::string&, bool silentsaylink);
int16 mod_pet_power(int16 act_power, uint16 spell_id);
float mod_tradeskill_chance(float chance, DBTradeskillRecipe_Struct *spec);
float mod_tradeskill_skillup(float chance_stage2);
int32 mod_tribute_item_value(int32 pts);
void mod_client_death_npc(Mob* killerMob);
void mod_client_death_duel(Mob* killerMob);
void mod_client_death_env();
protected: protected:
friend class Mob; friend class Mob;
void CalcItemBonuses(StatBonuses* newbon); void CalcItemBonuses(StatBonuses* newbon);
@ -1437,6 +1466,8 @@ private:
struct XTarget_Struct XTargets[XTARGET_HARDCAP]; struct XTarget_Struct XTargets[XTARGET_HARDCAP];
Timer ItemTickTimer;
std::map<std::string,std::string> accountflags;
}; };
#include "parser.h" #include "parser.h"

View File

@ -1415,6 +1415,8 @@ int Client::CalcHaste() {
h += spellbonuses.hastetype3; h += spellbonuses.hastetype3;
h += ExtraHaste; //GM granted haste. h += ExtraHaste; //GM granted haste.
h = mod_client_haste(h);
if (spellbonuses.inhibitmelee){ if (spellbonuses.inhibitmelee){
if (h >= 0) if (h >= 0)
h -= spellbonuses.inhibitmelee; h -= spellbonuses.inhibitmelee;

View File

@ -2076,6 +2076,29 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
} }
} }
int r;
bool tryaug = false;
ItemInst* clickaug = 0;
Item_Struct* augitem = 0;
for(r = 0; r < MAX_AUGMENT_SLOTS; r++) {
const ItemInst* aug_i = inst->GetAugment(r);
if(!aug_i)
continue;
const Item_Struct* aug = aug_i->GetItem();
if(!aug)
continue;
if ( (aug->Click.Type == ET_ClickEffect) || (aug->Click.Type == ET_Expendable) || (aug->Click.Type == ET_EquipClick) || (aug->Click.Type == ET_ClickEffect2) )
{
tryaug = true;
clickaug = (ItemInst*)aug_i;
augitem = (Item_Struct*)aug;
spell_id = aug->Click.Effect;
break;
}
}
if((spell_id <= 0) && (item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink && item->ItemType != ItemTypeAlcohol && item->ItemType != ItemTypeSpell)) if((spell_id <= 0) && (item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink && item->ItemType != ItemTypeAlcohol && item->ItemType != ItemTypeSpell))
{ {
LogFile->write(EQEMuLog::Debug, "Item with no effect right clicked by %s",GetName()); LogFile->write(EQEMuLog::Debug, "Item with no effect right clicked by %s",GetName());
@ -2123,6 +2146,39 @@ void Client::Handle_OP_ItemVerifyRequest(const EQApplicationPacket *app)
return; return;
} }
} }
else if (tryaug)
{
if (clickaug->GetCharges() == 0)
{
//Message(0, "This item is out of charges.");
Message_StringID(13, ITEM_OUT_OF_CHARGES);
return;
}
if(GetLevel() >= augitem->Click.Level2)
{
if(parse->ItemHasQuestSub(clickaug, "EVENT_ITEM_CLICK_CAST"))
{
//TODO: need to enforce and set recast timers here because the spell may not be cast.
parse->EventItem(EVENT_ITEM_CLICK_CAST, this, clickaug, clickaug->GetID(), slot_id);
inst = m_inv[slot_id];
if (!inst)
{
// Item was deleted by the perl event
return;
}
}
else
{
//We assume augs aren't consumable
CastSpell(augitem->Click.Effect, target_id, 10, augitem->CastTime, 0, 0, slot_id);
}
}
else
{
Message_StringID(13, ITEMS_INSUFFICIENT_LEVEL);
return;
}
}
else else
{ {
if(GetClientVersion() >= EQClientSoD && !inst->IsEquipable(GetBaseRace(),GetClass())) if(GetClientVersion() >= EQClientSoD && !inst->IsEquipable(GetBaseRace(),GetClass()))
@ -2605,6 +2661,8 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app)
con->faction = FACTION_DUBIOUS; con->faction = FACTION_DUBIOUS;
} }
mod_consider(tmob, con);
QueuePacket(outapp); QueuePacket(outapp);
safe_delete(outapp); safe_delete(outapp);
return; return;
@ -3200,6 +3258,8 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
if((response).size() > 0) if((response).size() > 0)
{ {
if( !mod_saylink(response, silentsaylink) ) { return; }
if(this->GetTarget() && this->GetTarget()->IsNPC()) if(this->GetTarget() && this->GetTarget()->IsNPC())
{ {
if(silentsaylink) if(silentsaylink)
@ -6069,7 +6129,7 @@ void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app)
" LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id " " LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id "
" LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " " LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id "
" WHERE tr.id IN (%s) " " WHERE tr.id IN (%s) "
" AND tr.must_learn & 0x20 <> 0x20 AND ((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT nullptr) OR (tr.must_learn & 0x3 = 0)) " " AND tr.must_learn & 0x20 <> 0x20 AND ((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) OR (tr.must_learn & 0x3 = 0)) "
" GROUP BY tr.id " " GROUP BY tr.id "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " " HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
" LIMIT 100 ", CharacterID(), buf, containers); " LIMIT 100 ", CharacterID(), buf, containers);
@ -6124,7 +6184,7 @@ void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app)
" LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id " " LEFT JOIN tradeskill_recipe_entries AS tre ON tr.id=tre.recipe_id "
" LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id " " LEFT JOIN (SELECT recipe_id, madecount FROM char_recipe_list WHERE char_id = %u) AS crl ON tr.id=crl.recipe_id "
" WHERE %s tr.trivial >= %u AND tr.trivial <= %u " " WHERE %s tr.trivial >= %u AND tr.trivial <= %u "
" AND tr.must_learn & 0x20 <> 0x20 AND((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT nullptr) OR (tr.must_learn & 0x3 = 0)) " " AND tr.must_learn & 0x20 <> 0x20 AND((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) OR (tr.must_learn & 0x3 = 0)) "
" GROUP BY tr.id " " GROUP BY tr.id "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 " " HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
" LIMIT 200 " " LIMIT 200 "
@ -7714,7 +7774,11 @@ void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app)
SetHP(GetHP() - damage); SetHP(GetHP() - damage);
if(GetHP() <= 0) if(GetHP() <= 0)
{
mod_client_death_env();
Death(0, 32000, SPELL_UNKNOWN, HAND_TO_HAND); Death(0, 32000, SPELL_UNKNOWN, HAND_TO_HAND);
}
SendHPUpdate(); SendHPUpdate();
return; return;
} }

View File

@ -645,6 +645,11 @@ bool Client::Process() {
--m_pp.intoxication; --m_pp.intoxication;
CalcBonuses(); CalcBonuses();
} }
if(ItemTickTimer.Check())
{
TickItemCheck();
}
} }
} }

View File

@ -88,7 +88,10 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_CLICK_OBJECT", "EVENT_CLICK_OBJECT",
"EVENT_DISCOVER_ITEM", "EVENT_DISCOVER_ITEM",
"EVENT_DISCONNECT", "EVENT_DISCONNECT",
"EVENT_CONNECT" "EVENT_CONNECT",
"EVENT_ITEM_TICK",
"EVENT_DUEL_WIN",
"EVENT_DUEL_LOSE"
}; };
PerlembParser::PerlembParser() : perl(nullptr) { PerlembParser::PerlembParser() : perl(nullptr) {

View File

@ -1888,6 +1888,12 @@ void EntityList::QueueClientsStatus(Mob* sender, const EQApplicationPacket* app,
void EntityList::DuelMessage(Mob* winner, Mob* loser, bool flee) { void EntityList::DuelMessage(Mob* winner, Mob* loser, bool flee) {
LinkedListIterator<Client*> iterator(client_list); LinkedListIterator<Client*> iterator(client_list);
if(winner->GetLevelCon(winner->GetLevel(), loser->GetLevel()) > 2)
{
parse->EventPlayer(EVENT_DUEL_WIN, winner->CastToClient(), loser->GetName(), loser->CastToClient()->CharacterID());
parse->EventPlayer(EVENT_DUEL_LOSE, loser->CastToClient(), winner->GetName(), winner->CastToClient()->CharacterID());
}
iterator.Reset(); iterator.Reset();
while(iterator.MoreElements()) { while(iterator.MoreElements()) {
Client *cur = iterator.GetData(); Client *cur = iterator.GetData();

View File

@ -58,6 +58,9 @@ typedef enum {
EVENT_DISCOVER_ITEM, EVENT_DISCOVER_ITEM,
EVENT_DISCONNECT, EVENT_DISCONNECT,
EVENT_CONNECT, EVENT_CONNECT,
EVENT_ITEM_TICK,
EVENT_DUEL_WIN,
EVENT_DUEL_LOSE,
_LargestEventID _LargestEventID
} QuestEventID; } QuestEventID;

View File

@ -289,8 +289,16 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) {
if(check_level > maxlevel) { if(check_level > maxlevel) {
check_level = maxlevel; check_level = maxlevel;
if(RuleB(Character, KeepLevelOverMax))
{
set_exp = GetEXPForLevel(GetLevel()+1);
}
else
{
set_exp = GetEXPForLevel(maxlevel); set_exp = GetEXPForLevel(maxlevel);
} }
}
if(RuleB(Character, PerCharacterQglobalMaxLevel)){ if(RuleB(Character, PerCharacterQglobalMaxLevel)){
uint32 MaxLevel = GetCharMaxLevelFromQGlobal(); uint32 MaxLevel = GetCharMaxLevelFromQGlobal();

View File

@ -376,3 +376,8 @@ void NPC::AddLootTable() {
} }
} }
void NPC::AddLootTable(uint32 ldid) {
if (npctype_id != 0) { // check if it's a GM spawn
database.AddLootTableToNPC(this,ldid, &itemlist, &copper, &silver, &gold, &platinum);
}
}

View File

@ -1975,7 +1975,7 @@ void Mob::SetAttackTimer() {
continue; continue;
if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == bagTypeQuiver) if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == bagTypeQuiver)
{ {
float temp_wr = (pi->GetItem()->BagWR / 3); float temp_wr = ( pi->GetItem()->BagWR / RuleI(Combat, QuiverWRHasteDiv) );
if(temp_wr > max_quiver) if(temp_wr > max_quiver)
{ {
max_quiver = temp_wr; max_quiver = temp_wr;
@ -3784,12 +3784,12 @@ void Mob::InsertQuestGlobal(int charid, int npcid, int zoneid, const char *varna
char *query = 0; char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE]; char errbuf[MYSQL_ERRMSG_SIZE];
// Make duration string either "unix_timestamp(now()) + xxx" or "nullptr" // Make duration string either "unix_timestamp(now()) + xxx" or "NULL"
stringstream duration_ss; stringstream duration_ss;
if (duration == INT_MAX) if (duration == INT_MAX)
{ {
duration_ss << "nullptr"; duration_ss << "NULL";
} }
else else
{ {
@ -4675,3 +4675,20 @@ FACTION_VALUE Mob::GetSpecialFactionCon(Mob* iOther) {
} }
} }
bool Mob::HasSpellEffect(int effectid)
{
int i;
uint32 buff_count = GetMaxTotalSlots();
for(i = 0; i < buff_count; i++)
{
if(buffs[i].spellid == SPELL_UNKNOWN) { continue; }
if(IsEffectInSpell(buffs[i].spellid, effectid))
{
return(1);
}
}
return(0);
}

View File

@ -789,6 +789,30 @@ public:
inline void SetEmoteID(uint16 emote) { emoteid = emote; } inline void SetEmoteID(uint16 emote) { emoteid = emote; }
inline uint16 GetEmoteID() { return emoteid; } inline uint16 GetEmoteID() { return emoteid; }
bool HasSpellEffect(int effectid);
int mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster);
float mod_hit_chance(float chancetohit, SkillType skillinuse, Mob* attacker);
float mod_riposte_chance(float ripostchance, Mob* attacker);
float mod_block_chance(float blockchance, Mob* attacker);
float mod_parry_chance(float parrychance, Mob* attacker);
float mod_dodge_chance(float dodgechance, Mob* attacker);
float mod_monk_weight(float monkweight, Mob* attacker);
float mod_mitigation_rating(float mitigation_rating, Mob* attacker);
float mod_attack_rating(float attack_rating, Mob* defender);
int32 mod_kick_damage(int32 dmg);
int32 mod_bash_damage(int32 dmg);
int32 mod_frenzy_damage(int32 dmg);
int32 mod_monk_special_damage(int32 ndamage, SkillType skill_type);
int32 mod_backstab_damage(int32 ndamage);
int mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon);
uint32 mod_archery_bonus_damage(uint32 MaxDmg);
int32 mod_archery_damage(int32 TotalDmg, bool hasbonus);
uint16 mod_throwing_damage(uint16 MaxDmg);
int32 mod_cast_time(int32 cast_time);
int mod_buff_duration(int res, Mob* caster, Mob* target, uint16 spell_id);
int mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2);
int mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster);
protected: protected:
void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic); void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic);
static uint16 GetProcID(uint16 spell_id, uint8 effect_index); static uint16 GetProcID(uint16 spell_id, uint8 effect_index);

72
zone/mod_functions.cpp Normal file
View File

@ -0,0 +1,72 @@
#include "../common/debug.h"
#include "../common/timer.h"
#include <cmath>
#include <stdlib.h>
#include "spawn2.h"
#include "entity.h"
#include "masterentity.h"
#include "zone.h"
#include "spawngroup.h"
#include "zonedb.h"
#include "npc.h"
#include "mob.h"
#include "client.h"
#include "worldserver.h"
#include "QuestParserCollection.h"
#include <string>
#include <iostream>
extern EntityList entity_list;
extern Zone* zone;
extern WorldServer worldserver;
using namespace std;
void Zone::mod_init() { return; }
void Zone::mod_repop() { return; }
void NPC::mod_prespawn(Spawn2 *sp) { return; }
int NPC::mod_npc_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other) { return(damage); }
void NPC::mod_npc_killed_merit(Mob* c) { return; }
void NPC::mod_npc_killed(Mob* oos) { return; }
int Client::mod_client_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other) { return(damage); }
bool Client::mod_client_message(char* message, uint8 chan_num) { return(true); } //Potentially dangerous string handling here
bool Client::mod_can_increase_skill(SkillType skillid, Mob* against_who) { return(false); }
int16 Client::mod_increase_skill_chance(int16 chance, Mob* against_who) { return(chance); }
int Client::mod_bindwound_percent(int max_percent, Mob* bindmob) { return(max_percent); }
int Client::mod_bindwound_hp(int bindhps, Mob* bindmob) { return(bindhps); }
int Client::mod_client_haste(int h) { return(h); }
void Client::mod_consider(Mob* tmob, Consider_Struct* con) { return; }
bool Client::mod_saylink(const std::string&, bool silentsaylink) { return(true); }
int16 Client::mod_pet_power(int16 act_power, uint16 spell_id) { return(act_power); }
float Client::mod_tradeskill_chance(float chance, DBTradeskillRecipe_Struct *spec) { return(chance); }
float Client::mod_tradeskill_skillup(float chance_stage2) { return(chance_stage2); }
int32 Client::mod_tribute_item_value(int32 pts) { return(pts); }
void Client::mod_client_death_npc(Mob* killerMob) { return; }
void Client::mod_client_death_duel(Mob* killerMob) { return; }
void Client::mod_client_death_env() { return; }
int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster) { return(effect_value); }
float Mob::mod_hit_chance(float chancetohit, SkillType skillinuse, Mob* attacker) { return(chancetohit); }
float Mob::mod_riposte_chance(float ripostechance, Mob* attacker) { return(ripostechance); }
float Mob::mod_block_chance(float blockchance, Mob* attacker) { return(blockchance); }
float Mob::mod_parry_chance(float parrychance, Mob* attacker) { return(parrychance); }
float Mob::mod_dodge_chance(float dodgechance, Mob* attacker) { return(dodgechance); }
float Mob::mod_monk_weight(float monkweight, Mob* attacker) { return(monkweight); }
float Mob::mod_mitigation_rating(float mitigation_rating, Mob* attacker) { return(mitigation_rating); }
float Mob::mod_attack_rating(float attack_rating, Mob* defender) { return(attack_rating); }
int32 Mob::mod_kick_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_bash_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_frenzy_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_monk_special_damage(int32 ndamage, SkillType skill_type) { return(ndamage); }
int32 Mob::mod_backstab_damage(int32 ndamage) { return(ndamage); }
int Mob::mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon) { return(bonuschance); }
uint32 Mob::mod_archery_bonus_damage(uint32 MaxDmg) { return(MaxDmg); }
int32 Mob::mod_archery_damage(int32 TotalDmg, bool hasbonus) { return(TotalDmg); }
uint16 Mob::mod_throwing_damage(uint16 MaxDmg) { return(MaxDmg); }
int32 Mob::mod_cast_time(int32 cast_time) { return(cast_time); }
int Mob::mod_buff_duration(int res, Mob* caster, Mob* target, uint16 spell_id) { return(res); }
int Mob::mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2) { return(2); }
int Mob::mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster) { return(resist_chance + level_mod + resist_modifier + target_resist); }

View File

@ -0,0 +1,72 @@
#include "../common/debug.h"
#include "../common/timer.h"
#include <cmath>
#include <stdlib.h>
#include "spawn2.h"
#include "entity.h"
#include "masterentity.h"
#include "zone.h"
#include "spawngroup.h"
#include "zonedb.h"
#include "npc.h"
#include "mob.h"
#include "client.h"
#include "worldserver.h"
#include "QuestParserCollection.h"
#include <string>
#include <iostream>
extern EntityList entity_list;
extern Zone* zone;
extern WorldServer worldserver;
using namespace std;
void Zone::mod_init() { return; }
void Zone::mod_repop() { return; }
void NPC::mod_prespawn(Spawn2 *sp) { return; }
int NPC::mod_npc_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other) { return(damage); }
void NPC::mod_npc_killed_merit(Mob* c) { return; }
void NPC::mod_npc_killed(Mob* oos) { return; }
int Client::mod_client_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other) { return(damage); }
bool Client::mod_client_message(char* message, uint8 chan_num) { return(true); } //Potentially dangerous string handling here
bool Client::mod_can_increase_skill(SkillType skillid, Mob* against_who) { return(false); }
int16 Client::mod_increase_skill_chance(int16 chance, Mob* against_who) { return(chance); }
int Client::mod_bindwound_percent(int max_percent, Mob* bindmob) { return(max_percent); }
int Client::mod_bindwound_hp(int bindhps, Mob* bindmob) { return(bindhps); }
int Client::mod_client_haste(int h) { return(h); }
void Client::mod_consider(Mob* tmob, Consider_Struct* con) { return; }
bool Client::mod_saylink(const std::string&, bool silentsaylink) { return(true); }
int16 Client::mod_pet_power(int16 act_power, uint16 spell_id) { return(act_power); }
float Client::mod_tradeskill_chance(float chance, DBTradeskillRecipe_Struct *spec) { return(chance); }
float Client::mod_tradeskill_skillup(float chance_stage2) { return(chance_stage2); }
int32 Client::mod_tribute_item_value(int32 pts) { return(pts); }
void Client::mod_client_death_npc(Mob* killerMob) { return; }
void Client::mod_client_death_duel(Mob* killerMob) { return; }
void Client::mod_client_death_env() { return; }
int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster) { return(effect_value); }
float Mob::mod_hit_chance(float chancetohit, SkillType skillinuse, Mob* attacker) { return(chancetohit); }
float Mob::mod_riposte_chance(float ripostechance, Mob* attacker) { return(ripostechance); }
float Mob::mod_block_chance(float blockchance, Mob* attacker) { return(blockchance); }
float Mob::mod_parry_chance(float parrychance, Mob* attacker) { return(parrychance); }
float Mob::mod_dodge_chance(float dodgechance, Mob* attacker) { return(dodgechance); }
float Mob::mod_monk_weight(float monkweight, Mob* attacker) { return(monkweight); }
float Mob::mod_mitigation_rating(float mitigation_rating, Mob* attacker) { return(mitigation_rating); }
float Mob::mod_attack_rating(float attack_rating, Mob* defender) { return(attack_rating); }
int32 Mob::mod_kick_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_bash_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_frenzy_damage(int32 dmg) { return(dmg); }
int32 Mob::mod_monk_special_damage(int32 ndamage, SkillType skill_type) { return(ndamage); }
int32 Mob::mod_backstab_damage(int32 ndamage) { return(ndamage); }
int Mob::mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon) { return(bonuschance); }
uint32 Mob::mod_archery_bonus_damage(uint32 MaxDmg) { return(MaxDmg); }
int32 Mob::mod_archery_damage(int32 TotalDmg, bool hasbonus) { return(TotalDmg); }
uint16 Mob::mod_throwing_damage(uint16 MaxDmg) { return(MaxDmg); }
int32 Mob::mod_cast_time(int32 cast_time) { return(cast_time); }
int Mob::mod_buff_duration(int res, Mob* caster, Mob* target, uint16 spell_id) { return(res); }
int Mob::mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2) { return(2); }
int Mob::mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster) { return(resist_chance + level_mod + resist_modifier + target_resist); }

View File

@ -1895,7 +1895,8 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue)
if(id == "special_attacks") if(id == "special_attacks")
{ {
NPCSpecialAttacks(val.c_str(), 0); //Added reset flag.
NPCSpecialAttacks(val.c_str(), 0, 1);
return; return;
} }
@ -2414,3 +2415,75 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) {
bool NPC::IsFactionListAlly(uint32 other_faction) { bool NPC::IsFactionListAlly(uint32 other_faction) {
return(CheckNPCFactionAlly(other_faction) == FACTION_ALLY); return(CheckNPCFactionAlly(other_faction) == FACTION_ALLY);
} }
int NPC::GetScore()
{
int lv = min(70, (int)GetLevel());
int basedmg = (lv*2)*(1+(lv / 100)) - (lv / 2);
int minx = 0;
int basehp = 0;
int hpcontrib = 0;
int dmgcontrib = 0;
int spccontrib = 0;
int hp = GetMaxHP();
int mindmg = min_dmg;
int maxdmg = max_dmg;
int final;
if(lv < 46)
{
minx = static_cast<int> (ceil( ((lv - (lv / 10.0)) - 1.0) ));
basehp = (lv * 10) + (lv * lv);
}
else
{
minx = static_cast<int> (ceil( ((lv - (lv / 10.0)) - 1.0) - (( lv - 45.0 ) / 2.0) ));
basehp = (lv * 10) + ((lv * lv) * 4);
}
if(hp > basehp)
{
hpcontrib = static_cast<int> (((hp / static_cast<float> (basehp)) * 1.5));
if(hpcontrib > 5) { hpcontrib = 5; }
if(maxdmg > basedmg)
{
dmgcontrib = static_cast<int> (ceil( ((maxdmg / basedmg) * 1.5) ));
}
if(HasNPCSpecialAtk("E")) { spccontrib++; } //Enrage
if(HasNPCSpecialAtk("F")) { spccontrib++; } //Flurry
if(HasNPCSpecialAtk("R")) { spccontrib++; } //Rampage
if(HasNPCSpecialAtk("r")) { spccontrib++; } //Area Rampage
if(HasNPCSpecialAtk("S")) { spccontrib++; } //Summon
if(HasNPCSpecialAtk("T")) { spccontrib += 2; } //Triple
if(HasNPCSpecialAtk("Q")) { spccontrib += 3; } //Quad
if(HasNPCSpecialAtk("U")) { spccontrib += 5; } //Unslowable
if(HasNPCSpecialAtk("L")) { spccontrib++; } //Innate Dual Wield
}
if(npc_spells_id > 12)
{
if(lv < 16)
spccontrib++;
else
spccontrib += static_cast<int> (floor(lv/15.0));
}
final = minx + hpcontrib + dmgcontrib + spccontrib;
final = max(1, final);
final = min(100, final);
return(final);
}
uint32 NPC::GetSpawnKillCount()
{
uint32 sid = GetSpawnPointID();
if(sid > 0)
{
return(zone->GetSpawnKillCount(sid));
}
return(0);
}

View File

@ -154,7 +154,7 @@ public:
void AddItem(const Item_Struct* item, uint16 charges, bool equipitem = true); void AddItem(const Item_Struct* item, uint16 charges, bool equipitem = true);
void AddItem(uint32 itemid, uint16 charges, bool equipitem = true); void AddItem(uint32 itemid, uint16 charges, bool equipitem = true);
void AddLootTable(); void AddLootTable();
void AddLootTable(uint32 ldid);
void DescribeAggro(Client *towho, Mob *mob, bool verbose); void DescribeAggro(Client *towho, Mob *mob, bool verbose);
void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0);
void CheckMinMaxLevel(Mob *them); void CheckMinMaxLevel(Mob *them);
@ -454,6 +454,13 @@ public:
void PrintOutQuestItems(Client* c); void PrintOutQuestItems(Client* c);
uint32 GetSpawnKillCount();
int GetScore();
void mod_prespawn(Spawn2 *sp);
int mod_npc_damage(int damage, SkillType skillinuse, int hand, ItemInst* weapon, Mob* other);
void mod_npc_killed_merit(Mob* c);
void mod_npc_killed(Mob* oos);
protected: protected:
const NPCType* NPCTypedata; const NPCType* NPCTypedata;

View File

@ -5651,6 +5651,91 @@ XS(XS_Client_GetInstanceID)
XSRETURN(1); XSRETURN(1);
} }
XS(XS_Client_HasSpellScribed); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_HasSpellScribed)
{
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::HasSpellScribed(THIS, spell_id)");
{
Client * THIS;
bool RETVAL;
int spell_id = (int)SvUV(ST(1));
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
RETVAL = THIS->HasSpellScribed(spell_id);
ST(0) = boolSV(RETVAL);
sv_2mortal(ST(0));
}
XSRETURN(1);
}
XS(XS_Client_SetAccountFlag); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SetAccountFlag)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: Client::SetAccountFlag(THIS, flag, value)");
{
Client * THIS;
//char* flag = (char *)SvPV_nolen(ST(1));
//char* value = (char *)SvTRUE(ST(2));
std::string flag( (char *)SvPV_nolen(ST(1)) );
std::string value( (char *)SvTRUE(ST(2)) );
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
THIS->SetAccountFlag(flag, value);
}
XSRETURN_EMPTY;
}
XS(XS_Client_GetAccountFlag); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_GetAccountFlag)
{
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: Client::GetAccountFlag(THIS, flag)");
{
Client * THIS;
//char* flag = (char *)SvPV_nolen(ST(1));
//char* value = (char *)SvTRUE(ST(2));
std::string flag( (char *)SvPV_nolen(ST(1)) );
dXSTARG;
if (sv_derived_from(ST(0), "Client")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Client *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Client");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
std::string value = THIS->GetAccountFlag(flag);
sv_setpv(TARG, value.c_str()); XSprePUSH; PUSHTARG;
}
XSRETURN(1);
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
#endif #endif
@ -5877,6 +5962,9 @@ XS(boot_Client)
newXSproto(strcpy(buf, "AddAlternateCurrencyValue"), XS_Client_AddAlternateCurrencyValue, file, "$$$"); newXSproto(strcpy(buf, "AddAlternateCurrencyValue"), XS_Client_AddAlternateCurrencyValue, file, "$$$");
newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, file, "$:$"); newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, file, "$:$");
newXSproto(strcpy(buf, "GetInstanceID"), XS_Client_GetInstanceID, file, "$$"); newXSproto(strcpy(buf, "GetInstanceID"), XS_Client_GetInstanceID, file, "$$");
newXSproto(strcpy(buf, "HasSpellScribed"), XS_Client_HasSkill, file, "$$");
newXSproto(strcpy(buf, "SetAccountFlag"), XS_Client_SetAccountFlag, file, "$$");
newXSproto(strcpy(buf, "GetAccountFlag"), XS_Client_GetAccountFlag, file, "$$");
XSRETURN_YES; XSRETURN_YES;
} }

View File

@ -3909,8 +3909,8 @@ XS(XS_Mob_CastSpell); /* prototype to pass -Wmissing-prototypes */
XS(XS_Mob_CastSpell) XS(XS_Mob_CastSpell)
{ {
dXSARGS; dXSARGS;
if (items < 3 || items > 6) if (items < 3 || items > 7)
Perl_croak(aTHX_ "Usage: Mob::CastSpell(THIS, spell_id, target_id, slot= 10, casttime= -1, mana_cost= -1)"); Perl_croak(aTHX_ "Usage: Mob::CastSpell(THIS, spell_id, target_id, slot= 10, casttime= -1, mana_cost= -1, resist_adjust = 0)");
{ {
Mob * THIS; Mob * THIS;
uint16 spell_id = (uint16)SvUV(ST(1)); uint16 spell_id = (uint16)SvUV(ST(1));
@ -3918,6 +3918,7 @@ XS(XS_Mob_CastSpell)
uint16 slot; uint16 slot;
int32 casttime; int32 casttime;
int32 mana_cost; int32 mana_cost;
int16 resist_adjust;
if (sv_derived_from(ST(0), "Mob")) { if (sv_derived_from(ST(0), "Mob")) {
IV tmp = SvIV((SV*)SvRV(ST(0))); IV tmp = SvIV((SV*)SvRV(ST(0)));
@ -3946,7 +3947,16 @@ XS(XS_Mob_CastSpell)
mana_cost = (int32)SvIV(ST(5)); mana_cost = (int32)SvIV(ST(5));
} }
THIS->CastSpell(spell_id, target_id, slot, casttime, mana_cost); if(items < 7)
{
resist_adjust = 0;
}
else
{
resist_adjust = (int16)SvIV(ST(6));
}
THIS->CastSpell(spell_id, target_id, slot, casttime, mana_cost, 0, 0xFFFFFFFF, 0xFFFFFFFF, 0, 0, &resist_adjust);
} }
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
@ -3955,13 +3965,14 @@ XS(XS_Mob_SpellFinished); /* prototype to pass -Wmissing-prototypes */
XS(XS_Mob_SpellFinished) XS(XS_Mob_SpellFinished)
{ {
dXSARGS; dXSARGS;
if (items < 2 || items > 4) if (items < 2 || items > 5)
Perl_croak(aTHX_ "Usage: Mob::SpellFinished(spell_id, spell_target = this, mana_cost = 0)"); Perl_croak(aTHX_ "Usage: Mob::SpellFinished(spell_id, spell_target = this, mana_cost = 0, resist_diff = 0)");
{ {
Mob * THIS; Mob * THIS;
uint16 spell_id = (uint16)SvUV(ST(1)); uint16 spell_id = (uint16)SvUV(ST(1));
Mob * spell_target; Mob * spell_target;
uint16 mana_cost = 0; uint16 mana_cost = 0;
int16 resist_diff;
if (sv_derived_from(ST(0), "Mob")) { if (sv_derived_from(ST(0), "Mob")) {
IV tmp = SvIV((SV*)SvRV(ST(0))); IV tmp = SvIV((SV*)SvRV(ST(0)));
@ -3990,7 +4001,16 @@ XS(XS_Mob_SpellFinished)
if (items > 3) if (items > 3)
mana_cost = (uint16)SvUV(ST(3)); mana_cost = (uint16)SvUV(ST(3));
THIS->SpellFinished(spell_id, spell_target, 10, mana_cost, -1, spells[spell_id].ResistDiff); if (items > 4)
{
resist_diff = (int16)SvUV(ST(4));
}
else
{
resist_diff = spells[spell_id].ResistDiff;
}
THIS->SpellFinished(spell_id, spell_target, 10, mana_cost, -1, resist_diff);
} }
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }

View File

@ -143,8 +143,18 @@ XS(XS_NPC_AddLootTable)
if(THIS == nullptr) if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
uint32 loottable_id = 0;
if(items > 1)
{
loottable_id = (uint32)SvUV(ST(1));
THIS->AddLootTable(loottable_id);
}
else
{
THIS->AddLootTable(); THIS->AddLootTable();
} }
}
XSRETURN_EMPTY; XSRETURN_EMPTY;
} }
@ -2075,6 +2085,57 @@ XS(XS_NPC_GetAccuracyRating)
XSRETURN(1); XSRETURN(1);
} }
XS(XS_NPC_GetSpawnKillCount); /* prototype to pass -Wmissing-prototypes */
XS(XS_NPC_GetSpawnKillCount)
{
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: NPC::GetSpawnKillCount(THIS)");
{
NPC * THIS;
uint32 RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
RETVAL = THIS->GetSpawnKillCount();
XSprePUSH; PUSHu((UV)RETVAL);
}
XSRETURN(1);
}
XS(XS_NPC_GetScore); /* prototype to pass -Wmissing-prototypes */
XS(XS_NPC_GetScore)
{
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: NPC::GetScore(THIS)");
{
NPC * THIS;
int RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
RETVAL = THIS->GetScore();
XSprePUSH; PUSHi((UV)RETVAL);
}
XSRETURN(1);
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
@ -2174,6 +2235,8 @@ XS(boot_NPC)
newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetAttackSpeed, file, "$"); newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetAttackSpeed, file, "$");
newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetSlowMitigation, file, "$"); newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetSlowMitigation, file, "$");
newXSproto(strcpy(buf, "GetAccuracyRating"), XS_NPC_GetAccuracyRating, file, "$"); newXSproto(strcpy(buf, "GetAccuracyRating"), XS_NPC_GetAccuracyRating, file, "$");
newXSproto(strcpy(buf, "GetSpawnKillCount"), XS_NPC_GetSpawnKillCount, file, "$");
newXSproto(strcpy(buf, "GetScore"), XS_NPC_GetScore, file, "$");
XSRETURN_YES; XSRETURN_YES;
} }

View File

@ -232,8 +232,10 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, c
int16 act_power = 0; // The actual pet power we'll use. int16 act_power = 0; // The actual pet power we'll use.
if (petpower == -1) { if (petpower == -1) {
if (this->IsClient()) if (this->IsClient()) {
act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);
act_power = CastToClient()->mod_pet_power(act_power, spell_id);
}
} }
else if (petpower > 0) else if (petpower > 0)
act_power = petpower; act_power = petpower;

View File

@ -833,7 +833,8 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
spells[curspell].classes[WARRIOR] != 0 && //check if spell exists spells[curspell].classes[WARRIOR] != 0 && //check if spell exists
spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level
spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level
spells[curspell].skill != 52 spells[curspell].skill != 52 &&
( RuleB(Spells, UseCHAScribeHack) && spells[curspell].effectid[EFFECT_COUNT - 1] != 10 )
) )
{ {
if (book_slot == -1) //no more book slots if (book_slot == -1) //no more book slots
@ -872,7 +873,8 @@ uint16 QuestManager::traindiscs(uint8 max_level, uint8 min_level) {
spells[curspell].classes[WARRIOR] != 0 && //check if spell exists spells[curspell].classes[WARRIOR] != 0 && //check if spell exists
spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level spells[curspell].classes[initiator->GetPP().class_-1] <= max_level && //maximum level
spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level spells[curspell].classes[initiator->GetPP().class_-1] >= min_level && //minimum level
spells[curspell].skill != 52 spells[curspell].skill != 52 &&
( RuleB(Spells, UseCHAScribeHack) && spells[curspell].effectid[EFFECT_COUNT - 1] != 10 )
) )
{ {
if(IsDiscipline(curspell)){ if(IsDiscipline(curspell)){
@ -1239,11 +1241,11 @@ int QuestManager::InsertQuestGlobal(
char *query = 0; char *query = 0;
char errbuf[MYSQL_ERRMSG_SIZE]; char errbuf[MYSQL_ERRMSG_SIZE];
// Make duration string either "unix_timestamp(now()) + xxx" or "nullptr" // Make duration string either "unix_timestamp(now()) + xxx" or "NULL"
stringstream duration_ss; stringstream duration_ss;
if (duration == INT_MAX) if (duration == INT_MAX)
{ {
duration_ss << "nullptr"; duration_ss << "NULL";
} }
else else
{ {

View File

@ -253,6 +253,8 @@ public:
bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender); bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender);
#endif #endif
inline uint16 GetMana(uint32 spell_id) { return( spells[spell_id].mana); }
protected: protected:
Mob *owner; //NPC is never nullptr when functions are called. Mob *owner; //NPC is never nullptr when functions are called.
Client *initiator; //this can be null. Client *initiator; //this can be null.

View File

@ -68,7 +68,7 @@ Spawn2::Spawn2(uint32 in_spawn2_id, uint32 spawngroup_id,
float in_x, float in_y, float in_z, float in_heading, float in_x, float in_y, float in_z, float in_heading,
uint32 respawn, uint32 variance, uint32 timeleft, uint32 grid, uint32 respawn, uint32 variance, uint32 timeleft, uint32 grid,
uint16 in_cond_id, int16 in_min_value, bool in_enabled, EmuAppearance anim) uint16 in_cond_id, int16 in_min_value, bool in_enabled, EmuAppearance anim)
: timer(100000) : timer(100000), killcount(0)
{ {
spawn2_id = in_spawn2_id; spawn2_id = in_spawn2_id;
spawngroup_id_ = spawngroup_id; spawngroup_id_ = spawngroup_id;
@ -219,6 +219,9 @@ bool Spawn2::Process() {
currentnpcid = npcid; currentnpcid = npcid;
NPC* npc = new NPC(tmp, this, x, y, z, heading, FlyMode3); NPC* npc = new NPC(tmp, this, x, y, z, heading, FlyMode3);
npc->mod_prespawn(this);
npcthis = npc; npcthis = npc;
npc->AddLootTable(); npc->AddLootTable();
npc->SetSp2(spawngroup_id_); npc->SetSp2(spawngroup_id_);
@ -328,7 +331,7 @@ void Spawn2::ForceDespawn()
} }
//resets our spawn as if we just died //resets our spawn as if we just died
void Spawn2::DeathReset() void Spawn2::DeathReset(bool realdeath)
{ {
//get our reset based on variance etc and store it locally //get our reset based on variance etc and store it locally
uint32 cur = resetTimer(); uint32 cur = resetTimer();
@ -338,6 +341,8 @@ void Spawn2::DeathReset()
//zero out our NPC since he is now gone //zero out our NPC since he is now gone
npcthis = nullptr; npcthis = nullptr;
if(realdeath) { killcount++; }
//if we have a valid spawn id //if we have a valid spawn id
if(spawn2_id) if(spawn2_id)
{ {

View File

@ -49,7 +49,7 @@ public:
void Repop(uint32 delay = 0); void Repop(uint32 delay = 0);
void ForceDespawn(); void ForceDespawn();
void DeathReset(); //resets the spawn in the case the npc dies, also updates db if needed void DeathReset(bool realdeath = 0); //resets the spawn in the case the npc dies, also updates db if needed
void SpawnConditionChanged(const SpawnCondition &c, int16 old_value); void SpawnConditionChanged(const SpawnCondition &c, int16 old_value);
uint32 GetID() { return spawn2_id; } uint32 GetID() { return spawn2_id; }
@ -71,6 +71,7 @@ public:
bool NPCPointerValid() { return (npcthis!=nullptr); } bool NPCPointerValid() { return (npcthis!=nullptr); }
void SetNPCPointer(NPC* n) { npcthis = n; } void SetNPCPointer(NPC* n) { npcthis = n; }
void SetTimer(uint32 duration) { timer.Start(duration); } void SetTimer(uint32 duration) { timer.Start(duration); }
uint32 GetKillCount() { return killcount; }
protected: protected:
friend class Zone; friend class Zone;
Timer timer; Timer timer;
@ -92,6 +93,7 @@ private:
bool enabled; bool enabled;
EmuAppearance anim; EmuAppearance anim;
bool IsDespawned; bool IsDespawned;
uint32 killcount;
}; };
class SpawnCondition { class SpawnCondition {

View File

@ -48,6 +48,8 @@ int Mob::GetKickDamage() {
int32 mindmg = 1; int32 mindmg = 1;
ApplySpecialAttackMod(KICK, dmg,mindmg); ApplySpecialAttackMod(KICK, dmg,mindmg);
dmg = mod_kick_damage(dmg);
return(dmg); return(dmg);
} }
@ -67,6 +69,8 @@ int Mob::GetBashDamage() {
int32 mindmg = 1; int32 mindmg = 1;
ApplySpecialAttackMod(BASH, dmg, mindmg); ApplySpecialAttackMod(BASH, dmg, mindmg);
dmg = mod_bash_damage(dmg);
return(dmg); return(dmg);
} }
@ -281,6 +285,8 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) {
int32 min_dmg = 0; int32 min_dmg = 0;
DoAnim(anim2HSlashing); DoAnim(anim2HSlashing);
max_dmg = mod_frenzy_damage(max_dmg);
if (GetLevel() < 51) if (GetLevel() < 51)
min_dmg = 1; min_dmg = 1;
else else
@ -491,6 +497,9 @@ int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type)
} }
} }
//This can potentially stack with changes to kick damage
ht = ndamage = mod_monk_special_damage(ndamage, skill_type);
DoSpecialAttackDamage(other, skill_type, ndamage, min_dmg, ht, reuse); DoSpecialAttackDamage(other, skill_type, ndamage, min_dmg, ht, reuse);
return(reuse); return(reuse);
@ -663,6 +672,8 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime)
ndamage = -5; ndamage = -5;
} }
ndamage = mod_backstab_damage(ndamage);
DoSpecialAttackDamage(other, BACKSTAB, ndamage, min_hit, hate, ReuseTime); DoSpecialAttackDamage(other, BACKSTAB, ndamage, min_hit, hate, ReuseTime);
DoAnim(animPiercing); DoAnim(animPiercing);
} }
@ -879,22 +890,34 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); mlog(COMBAT__RANGED, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg);
bool dobonus = false;
if(GetClass() == RANGER && GetLevel() > 50) if(GetClass() == RANGER && GetLevel() > 50)
{
int bonuschance = RuleI(Combat, ArcheryBonusChance);
bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon);
if( !RuleB(Combat, UseArcheryBonusRoll) || (MakeRandomInt(1, 100) < bonuschance) )
{ {
if(RuleB(Combat, ArcheryBonusRequiresStationary)) if(RuleB(Combat, ArcheryBonusRequiresStationary))
{ {
if(other->IsNPC() && !other->IsMoving() && !other->IsRooted()) if(other->IsNPC() && !other->IsMoving() && !other->IsRooted())
{ {
MaxDmg *= (float)2; dobonus = true;
hate *= (float)2;
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
} }
} }
else else
{
dobonus = true;
}
}
if(dobonus)
{ {
MaxDmg *= (float)2; MaxDmg *= (float)2;
hate *= (float)2; hate *= (float)2;
MaxDmg = mod_archery_bonus_damage(MaxDmg);
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg); mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
} }
@ -925,6 +948,9 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
ApplyMeleeDamageBonus(ARCHERY, TotalDmg); ApplyMeleeDamageBonus(ARCHERY, TotalDmg);
TotalDmg += other->GetAdditionalDamage(this, 0, true, ARCHERY); TotalDmg += other->GetAdditionalDamage(this, 0, true, ARCHERY);
TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(ARCHERY) / 100) + GetSkillDmgAmt(ARCHERY); TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(ARCHERY) / 100) + GetSkillDmgAmt(ARCHERY);
TotalDmg = mod_archery_damage(TotalDmg, dobonus);
TryCriticalHit(other, ARCHERY, TotalDmg); TryCriticalHit(other, ARCHERY, TotalDmg);
other->AddToHateList(this, hate, 0, false); other->AddToHateList(this, hate, 0, false);
} }
@ -941,6 +967,12 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
{ {
TryWeaponProc(RangeWeapon, other, 11); TryWeaponProc(RangeWeapon, other, 11);
} }
//Arrow procs because why not?
if((Ammo != NULL) && GetTarget() && other && (other->GetHP() > -10))
{
TryWeaponProc(Ammo, other, 11);
}
} }
void NPC::RangedAttack(Mob* other) void NPC::RangedAttack(Mob* other)
@ -1088,6 +1120,8 @@ uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg)
if(MaxDmg < minDmg) if(MaxDmg < minDmg)
MaxDmg = minDmg; MaxDmg = minDmg;
MaxDmg = mod_throwing_damage(MaxDmg);
return MaxDmg; return MaxDmg;
} }

View File

@ -627,7 +627,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
#endif #endif
// EverHood // EverHood
if(caster && GetPrimaryFaction()>0) { if(caster && GetPrimaryFaction()>0) {
caster->AddFactionBonus(GetPrimaryFaction(),spell.base[0]); caster->AddFactionBonus(GetPrimaryFaction(),effect_value);
} }
break; break;
} }
@ -2809,6 +2809,8 @@ int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level,
mlog(SPELLS__BARDS, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value); mlog(SPELLS__BARDS, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value);
} }
effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster);
return(effect_value); return(effect_value);
} }
@ -2816,6 +2818,7 @@ int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level,
int Mob::CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining) int Mob::CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining)
{ {
/* /*
neotokyo: i need those formulas checked!!!!
0 = base 0 = base
1 - 99 = base + level * formulaID 1 - 99 = base + level * formulaID
@ -2844,9 +2847,6 @@ int Mob::CalcSpellEffectValue_formula(int formula, int base, int max, int caster
int result = 0, updownsign = 1, ubase = base; int result = 0, updownsign = 1, ubase = base;
if(ubase < 0) if(ubase < 0)
ubase = 0 - ubase; ubase = 0 - ubase;
int level_diff = caster_level - GetMinLevel(spell_id);
if (level_diff < 0)
level_diff = 0;
// this updown thing might look messed up but if you look at the // this updown thing might look messed up but if you look at the
// spells it actually looks like some have a positive base and max where // spells it actually looks like some have a positive base and max where
@ -2903,23 +2903,23 @@ snare has both of them negative, yet their range should work the same:
result = ubase + (caster_level / 5); break; result = ubase + (caster_level / 5); break;
case 111: case 111:
result = updownsign * (ubase + 6 * level_diff); break; result = updownsign * (ubase + 6 * (caster_level - GetMinLevel(spell_id))); break;
case 112: case 112:
result = updownsign * (ubase + 8 * level_diff); break; result = updownsign * (ubase + 8 * (caster_level - GetMinLevel(spell_id))); break;
case 113: case 113:
result = updownsign * (ubase + 10 * level_diff); break; result = updownsign * (ubase + 10 * (caster_level - GetMinLevel(spell_id))); break;
case 114: case 114:
result = updownsign * (ubase + 15 * level_diff); break; result = updownsign * (ubase + 15 * (caster_level - GetMinLevel(spell_id))); break;
//these formula were updated according to lucy 10/16/04 //these formula were updated according to lucy 10/16/04
case 115: // this is only in symbol of transal case 115: // this is only in symbol of transal
result = ubase + 6 * level_diff; break; result = ubase + 6 * (caster_level - GetMinLevel(spell_id)); break;
case 116: // this is only in symbol of ryltan case 116: // this is only in symbol of ryltan
result = ubase + 8 * level_diff; break; result = ubase + 8 * (caster_level - GetMinLevel(spell_id)); break;
case 117: // this is only in symbol of pinzarn case 117: // this is only in symbol of pinzarn
result = ubase + 12 * level_diff; break; result = ubase + 12 * (caster_level - GetMinLevel(spell_id)); break;
case 118: // used in naltron and a few others case 118: // used in naltron and a few others
result = ubase + 20 * level_diff; break; result = ubase + 20 * (caster_level - GetMinLevel(spell_id)); break;
case 119: // confirmed 2/6/04 case 119: // confirmed 2/6/04
result = ubase + (caster_level / 8); break; result = ubase + (caster_level / 8); break;

View File

@ -440,6 +440,8 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot,
return(true); return(true);
} }
cast_time = mod_cast_time(cast_time);
// ok we know it has a cast time so we can start the timer now // ok we know it has a cast time so we can start the timer now
spellend_timer.Start(cast_time); spellend_timer.Start(cast_time);
@ -1099,11 +1101,53 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot,
if(IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT)) if(IsClient() && ((slot == USE_ITEM_SPELL_SLOT) || (slot == POTION_BELT_SPELL_SLOT))
&& inventory_slot != 0xFFFFFFFF) // 10 is an item && inventory_slot != 0xFFFFFFFF) // 10 is an item
{ {
bool fromaug = false;
const ItemInst* inst = CastToClient()->GetInv()[inventory_slot]; const ItemInst* inst = CastToClient()->GetInv()[inventory_slot];
if (inst && inst->IsType(ItemClassCommon) && (inst->GetItem()->Click.Effect == spell_id) && inst->GetCharges()) Item_Struct* augitem = 0;
uint32 recastdelay = 0;
uint32 recasttype = 0;
for(int r = 0; r < MAX_AUGMENT_SLOTS; r++) {
const ItemInst* aug_i = inst->GetAugment(r);
if(!aug_i)
continue;
const Item_Struct* aug = aug_i->GetItem();
if(!aug)
continue;
if ( aug->Click.Effect == spell_id )
{
recastdelay = aug_i->GetItem()->RecastDelay;
recasttype = aug_i->GetItem()->RecastType;
fromaug = true;
break;
}
}
//Test the aug recast delay
if(IsClient() && fromaug && recastdelay > 0)
{
if(!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recasttype), false)) {
Message_StringID(13, SPELL_RECAST);
mlog(SPELLS__CASTING_ERR, "Casting of %d canceled: item spell reuse timer not expired", spell_id);
InterruptSpell();
return;
}
else
{
//Can we start the timer here? I don't see why not.
CastToClient()->GetPTimers().Start((pTimerItemStart + recasttype), recastdelay);
}
}
if (inst && inst->IsType(ItemClassCommon) && (inst->GetItem()->Click.Effect == spell_id) && inst->GetCharges() || fromaug)
{ {
//const Item_Struct* item = inst->GetItem(); //const Item_Struct* item = inst->GetItem();
int16 charges = inst->GetItem()->MaxCharges; int16 charges = inst->GetItem()->MaxCharges;
if(fromaug) { charges = -1; } //Don't destroy the parent item
if(charges > -1) { // charged item, expend a charge if(charges > -1) { // charged item, expend a charge
mlog(SPELLS__CASTING, "Spell %d: Consuming a charge from item %s (%d) which had %d/%d charges.", spell_id, inst->GetItem()->Name, inst->GetItem()->ID, inst->GetCharges(), inst->GetItem()->MaxCharges); mlog(SPELLS__CASTING, "Spell %d: Consuming a charge from item %s (%d) which had %d/%d charges.", spell_id, inst->GetItem()->Name, inst->GetItem()->ID, inst->GetCharges(), inst->GetItem()->MaxCharges);
DeleteChargeFromSlot = inventory_slot; DeleteChargeFromSlot = inventory_slot;
@ -2328,6 +2372,8 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste
IsBlindSpell(spell_id)) IsBlindSpell(spell_id))
res += 1; res += 1;
res = mod_buff_duration(res, caster, target, spell_id);
mlog(SPELLS__CASTING, "Spell %d: Casting level %d, formula %d, base_duration %d: result %d", mlog(SPELLS__CASTING, "Spell %d: Casting level %d, formula %d, base_duration %d: result %d",
spell_id, castlevel, formula, duration, res); spell_id, castlevel, formula, duration, res);
@ -2431,6 +2477,9 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
return -1; return -1;
} }
int modval = mod_spell_stack(spellid1, caster_level1, caster1, spellid2, caster_level2, caster2);
if(modval < 2) { return(modval); }
//resurrection effects wont count for overwrite/block stacking //resurrection effects wont count for overwrite/block stacking
switch(spellid1) switch(spellid1)
{ {
@ -4055,19 +4104,6 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
level_mod = 1000; level_mod = 1000;
} }
//Add our level, resist and -spell resist modifier to our roll chance
resist_chance += level_mod;
resist_chance += resist_modifier;
resist_chance += target_resist;
if (CharismaCheck)
{
//For charm chance to break checks, Default 10 CHA = -1 resist mod.
int16 cha_resist_modifier = 0;
cha_resist_modifier = caster->GetCHA()/RuleI(Spells, CharismaEffectiveness);
resist_chance -= cha_resist_modifier;
}
//Even more level stuff this time dealing with damage spells //Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
{ {
@ -4084,9 +4120,24 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
{ {
level_diff = GetLevel() - caster->GetLevel(); level_diff = GetLevel() - caster->GetLevel();
} }
resist_chance += (2 * level_diff); level_mod += (2 * level_diff);
} }
if (CharismaCheck)
{
//For charm chance to break checks, Default 10 CHA = -1 resist mod.
int16 cha_resist_modifier = 0;
cha_resist_modifier = caster->GetCHA()/RuleI(Spells, CharismaEffectiveness);
resist_modifier -= cha_resist_modifier;
}
//Add our level, resist and -spell resist modifier to our roll chance
resist_chance += level_mod;
resist_chance += resist_modifier;
resist_chance += target_resist;
resist_chance = mod_spell_resist(resist_chance, level_mod, resist_modifier, target_resist, resist_type, spell_id, caster);
//Do our min and max resist checks. //Do our min and max resist checks.
if(resist_chance > spells[spell_id].MaxResist && spells[spell_id].MaxResist != 0) if(resist_chance > spells[spell_id].MaxResist && spells[spell_id].MaxResist != 0)
{ {

View File

@ -1158,7 +1158,7 @@ void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *Task
if(Tasks[TaskList[i]] != nullptr) break; if(Tasks[TaskList[i]] != nullptr) break;
} }
// FIXME: The 10 and 5 values in this calculation are to account for the string "nullptr" we are putting in 3 times. // FIXME: The 10 and 5 values in this calculation are to account for the string "ABCD" we are putting in 3 times.
// //
// Calculate how big the packet needs to be pased on the number of tasks and the // Calculate how big the packet needs to be pased on the number of tasks and the
// size of the variable length strings. // size of the variable length strings.
@ -1236,9 +1236,9 @@ void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *Task
// FIXME: In live packets, these two strings appear to be the same as the Text1 and Text2 // FIXME: In live packets, these two strings appear to be the same as the Text1 and Text2
// strings from the first activity in the task, however the task chooser/selector // strings from the first activity in the task, however the task chooser/selector
// does not appear to make use of them. // does not appear to make use of them.
sprintf(Ptr, "nullptr"); sprintf(Ptr, "ABCD");
Ptr = Ptr + strlen(Ptr) + 1; Ptr = Ptr + strlen(Ptr) + 1;
sprintf(Ptr, "nullptr"); sprintf(Ptr, "ABCD");
Ptr = Ptr + strlen(Ptr) + 1; Ptr = Ptr + strlen(Ptr) + 1;
AvailableTaskTrailer = (AvailableTaskTrailer_Struct*)Ptr; AvailableTaskTrailer = (AvailableTaskTrailer_Struct*)Ptr;
@ -1253,7 +1253,7 @@ void TaskManager::SendTaskSelector(Client *c, Mob *mob, int TaskCount, int *Task
// In some packets, this next string looks like a short task summary, however it doesn't // In some packets, this next string looks like a short task summary, however it doesn't
// appear anywhere in the client window. // appear anywhere in the client window.
sprintf(Ptr, "nullptr"); sprintf(Ptr, "ABCD");
Ptr = Ptr + strlen(Ptr) + 1; Ptr = Ptr + strlen(Ptr) + 1;
} }

View File

@ -1016,6 +1016,8 @@ bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) {
} }
} }
chance = mod_tradeskill_chance(chance, spec);
if (((spec->tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < AAChance){ if (((spec->tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < AAChance){
success_modifier = 1; success_modifier = 1;
@ -1108,6 +1110,8 @@ void Client::CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float
} }
} }
chance_stage2 = mod_tradeskill_skillup(chance_stage2);
if (chance_stage2 > MakeRandomFloat(0, 99)) { if (chance_stage2 > MakeRandomFloat(0, 99)) {
//Only if stage1 and stage2 succeeded you get a skillup. //Only if stage1 and stage2 succeeded you get a skillup.
SetSkill(tradeskill, current_raw_skill + 1); SetSkill(tradeskill, current_raw_skill + 1);

View File

@ -251,6 +251,9 @@ int32 Client::TributeItem(uint32 slot, uint32 quantity) {
//figure out what its worth //figure out what its worth
int32 pts = inst->GetItem()->Favor; int32 pts = inst->GetItem()->Favor;
pts = mod_tribute_item_value(pts);
if(pts < 1) { if(pts < 1) {
Message(13, "This item is worthless for favor."); Message(13, "This item is worthless for favor.");
return(0); return(0);

View File

@ -917,7 +917,8 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
autoshutdown_timer((RuleI(Zone, AutoShutdownDelay))), autoshutdown_timer((RuleI(Zone, AutoShutdownDelay))),
clientauth_timer(AUTHENTICATION_TIMEOUT * 1000), clientauth_timer(AUTHENTICATION_TIMEOUT * 1000),
spawn2_timer(1000), spawn2_timer(1000),
qglobal_purge_timer(30000) qglobal_purge_timer(30000),
hotzone_timer(120000)
{ {
zoneid = in_zoneid; zoneid = in_zoneid;
instanceid = in_instanceid; instanceid = in_instanceid;
@ -1164,6 +1165,12 @@ bool Zone::Init(bool iStaticZone) {
zone->zone_time.setEQTimeZone(database.GetZoneTZ(zoneid, GetInstanceVersion())); zone->zone_time.setEQTimeZone(database.GetZoneTZ(zoneid, GetInstanceVersion()));
LogFile->write(EQEMuLog::Status, "Init Finished: ZoneID = %d, Time Offset = %d", zoneid, zone->zone_time.getEQTimeZone()); LogFile->write(EQEMuLog::Status, "Init Finished: ZoneID = %d, Time Offset = %d", zoneid, zone->zone_time.getEQTimeZone());
LoadTickItems();
//MODDING HOOK FOR ZONE INIT
mod_init();
return true; return true;
} }
@ -1483,6 +1490,8 @@ bool Zone::Process() {
} }
} }
if(hotzone_timer.Check()) { UpdateHotzone(); }
return true; return true;
} }
@ -1531,6 +1540,9 @@ void Zone::Repop(uint32 delay) {
MZoneLock.unlock(); MZoneLock.unlock();
initgrids_timer.Start(); initgrids_timer.Start();
//MODDING HOOK FOR REPOP
mod_repop();
} }
void Zone::GetTimeSync() void Zone::GetTimeSync()
@ -2657,3 +2669,87 @@ void Zone::ReloadWorld(uint32 Option){
parse->ReloadQuests(); parse->ReloadQuests();
} }
} }
void Zone::LoadTickItems()
{
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
#if _MSC_VER==1600
item_tick_struct ti_tmp;
#endif
tick_items.clear();
//tick_globals.clear();
if(database.RunQuery(query, MakeAnyLenString(&query, "SELECT it_itemid, it_chance, it_level, it_qglobal, it_bagslot FROM item_tick"), errbuf, &result))
{
while((row = mysql_fetch_row(result)))
{
if(atoi(row[0]) < 1)
{
//tick_globals[std::string(row[0])] = { 0, atoi(row[1]), atoi(row[2]), (int16)atoi(row[4]), std::string(row[3]) };
}
else
#if _MSC_VER==1600
{
ti_tmp.itemid = atoi(row[0]);
ti_tmp.chance = atoi(row[1]);
ti_tmp.level = atoi(row[2]);
ti_tmp.bagslot = (int16)atoi(row[4]);
ti_tmp.qglobal = std::string(row[3]);
tick_items[atoi(row[0])] = ti_tmp;
}
#else
{
tick_items[atoi(row[0])] = { atoi(row[0]), atoi(row[1]), atoi(row[2]), (int16)atoi(row[4]), std::string(row[3]) };
}
#endif
}
mysql_free_result(result);
safe_delete_array(query);
}
else
{
LogFile->write(EQEMuLog::Error, "Error in Zone::LoadTickItems: %s (%s)", query, errbuf);
safe_delete_array(query);
}
}
uint32 Zone::GetSpawnKillCount(uint32 in_spawnid) {
LinkedListIterator<Spawn2*> iterator(spawn2_list);
iterator.Reset();
while(iterator.MoreElements())
{
if(iterator.GetData()->GetID() == in_spawnid)
{
return(iterator.GetData()->killcount);
}
iterator.Advance();
}
}
void Zone::UpdateHotzone()
{
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
bool updh;
if(database.RunQuery(query, MakeAnyLenString(&query,"SELECT hotzone FROM zone WHERE short_name = '%s'", GetShortName()), errbuf, &result) )
{
if( (row = mysql_fetch_row(result)) )
{
updh = atoi(row[0]) == 0 ? false:true;
//Hotzone status has changed
if(is_hotzone != updh)
{
is_hotzone = updh;
}
}
mysql_free_result(result);
}
safe_delete_array(query);
}

View File

@ -33,6 +33,7 @@
#include "tasks.h" #include "tasks.h"
#include "pathing.h" #include "pathing.h"
#include "QGlobals.h" #include "QGlobals.h"
#include <unordered_map>
class Map; class Map;
class WaterMap; class WaterMap;
@ -69,6 +70,14 @@ struct ZoneEXPModInfo {
float AAExpMod; float AAExpMod;
}; };
struct item_tick_struct {
uint32 itemid;
uint32 chance;
uint32 level;
int16 bagslot;
std::string qglobal;
};
extern EntityList entity_list; extern EntityList entity_list;
class database; class database;
class PathManager; class PathManager;
@ -253,6 +262,15 @@ public:
LinkedList<NPC_Emote_Struct*> NPCEmoteList; LinkedList<NPC_Emote_Struct*> NPCEmoteList;
void LoadTickItems();
uint32 GetSpawnKillCount(uint32 in_spawnid);
void UpdateHotzone();
unordered_map<int, item_tick_struct> tick_items;
//MODDING HOOKS
void mod_init();
void mod_repop();
private: private:
uint32 zoneid; uint32 zoneid;
uint32 instanceid; uint32 instanceid;
@ -305,6 +323,7 @@ private:
LinkedList<ZoneClientAuth_Struct*> client_auth_list; LinkedList<ZoneClientAuth_Struct*> client_auth_list;
QGlobalCache *qGlobals; QGlobalCache *qGlobals;
Timer hotzone_timer;
Mutex MZoneLock; Mutex MZoneLock;
}; };