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)
-------------------------------------------------------
== 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 ==
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 *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;
safe_delete_array(query);
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 << "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_status` `min_status` TINYINT(3) UNSIGNED DEFAULT \"0\" NOT nullptr;\n";
cerr << "ALTER TABLE `zone` ADD flag_needed VARCHAR(128) NOT nullptr DEFAULT '';\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 NULL;\n";
cerr << "ALTER TABLE `zone` ADD flag_needed VARCHAR(128) NOT NULL DEFAULT '';\n";
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, 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, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap
RULE_CATEGORY_END()
RULE_CATEGORY( Mercs )
@ -291,7 +291,8 @@ RULE_INT ( Spells, MaxCastTimeReduction, 50) //Max percent your spell cast time
RULE_INT ( Spells, RootBreakFromSpells, 20) //Chance for root to break when cast on.
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_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( 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_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_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( NPC )

View File

@ -1102,3 +1102,7 @@ bool IsShortDurationBuff(uint16 spell_id)
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);
int32 GetFuriousBash(uint16 spell_id);
bool IsShortDurationBuff(uint16 spell_id);
const char *GetSpellName(int16 spell_id);
#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);
}
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

View File

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

View File

@ -722,11 +722,24 @@ void Console::ProcessCommand(const char* command) {
}
}
else if (strcasecmp(sep.arg[0], "worldshutdown") == 0 && admin >= consoleWorldStatus) {
ServerPacket* pack = new ServerPacket(ServerOP_ShutdownAll);
zoneserver_list.SendPacket(pack);
delete pack;
SendMessage(1, "Sending shutdown packet... goodbye.");
CatchSignal(0);
int32 time, interval;
if(sep.IsNumber(1) && sep.IsNumber(2) && ((time=atoi(sep.arg[1]))>0) && ((interval=atoi(sep.arg[2]))>0)) {
zoneserver_list.WorldShutDown(time, interval);
}
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) {
WorldConfig::LockWorld();

View File

@ -969,6 +969,57 @@ XS(XS_EQW_ResolveBug)
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
extern "C"
@ -1020,6 +1071,8 @@ XS(boot_EQW)
newXSproto(strcpy(buf, "ListBugs"), XS_EQW_ListBugs, file, "$$");
newXSproto(strcpy(buf, "GetBugDetails"), XS_EQW_GetBugDetails, 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;
}

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();
void GetZoneIDList(std::vector<uint32> &zones);
void WorldShutDown(uint32 time, uint32 interval);
protected:
uint32 NextID;

View File

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

View File

@ -318,6 +318,8 @@ bool Mob::CheckHitChance(Mob* other, SkillType skillinuse, int Hand, int16 chanc
if(skillinuse == ARCHERY)
chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f;
chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker);
// Chance to hit; Max 95%, Min 30%
if(chancetohit > 1000) {
//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
bonus = 2.0 + skill/60.0 + (GetDEX()/200);
bonus *= riposte_chance;
bonus = mod_riposte_chance(bonus, attacker);
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
bonus = 2.0 + skill/35.0 + (GetDEX()/200);
bonus = mod_block_chance(bonus, attacker);
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
bonus = 2.0 + skill/60.0 + (GetDEX()/200);
bonus *= parry_chance;
bonus = mod_parry_chance(bonus, attacker);
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
bonus = 2.0 + skill/60.0 + (GetAGI()/200);
bonus *= dodge_chance;
//DCBOOMKAR
bonus = mod_dodge_chance(bonus, attacker);
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 armor = 0;
float weight = 0.0;
float monkweight = RuleI(Combat, MonkACBonusWeight);
monkweight = mod_monk_weight(monkweight, attacker);
if(IsClient())
{
armor = CastToClient()->GetRawACNoShield(shield_ac);
@ -576,7 +587,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
{
softcap = RuleI(Combat, ClothACSoftcap);
}
else if(GetClass() == MONK && weight <= 15.0)
else if(GetClass() == MONK && weight <= monkweight)
{
softcap = RuleI(Combat, MonkACSoftcap);
}
@ -603,7 +614,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
{
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);
}
@ -637,6 +648,8 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
}
mitigation_rating *= 0.847;
mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker);
if(attacker->IsClient())
{
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->mod_attack_rating(attack_rating, this);
float d = 10.0;
float mit_roll = MakeRandomFloat(0, mitigation_rating);
float atk_roll = MakeRandomFloat(0, attack_rating);
@ -669,6 +684,9 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
if(thac20 > thac20cap)
{
thac20 = thac20cap;
}
d += 10 * (m_diff / thac20);
}
@ -1221,6 +1239,8 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
else
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)",
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()) {
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0);
mod_client_death_npc(killerMob);
uint16 emoteid = killerMob->GetEmoteID();
if(emoteid != 0)
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()->SetDuelTarget(0);
entity_list.DuelMessage(killerMob,this,false);
mod_client_death_duel(killerMob);
} else {
//otherwise, we just died, end the duel.
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 = mod_npc_damage(damage, skillinuse, Hand, &weapon_inst, other);
int32 hate = damage;
if(IsPet())
{
@ -2043,7 +2071,7 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
entity_list.QueueClients(killerMob, app, false);
if(respawn2) {
respawn2->DeathReset();
respawn2->DeathReset(1);
}
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++) {
if (kr->members[i].member != nullptr) { // If Group Member is Client
parse->EventNPC(EVENT_KILLED_MERIT, this, kr->members[i].member, "killed", 0);
mod_npc_killed_merit(kr->members[i].member);
if(RuleB(TaskSystem, EnableTaskSystem))
kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID());
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
Client *c = kg->members[i]->CastToClient();
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
mod_npc_killed_merit(c);
if(RuleB(TaskSystem, EnableTaskSystem))
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 */
parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0);
mod_npc_killed_merit(give_exp_client);
if(RuleB(TaskSystem, EnableTaskSystem))
give_exp_client->UpdateTasksOnKill(GetNPCTypeID());
@ -2312,6 +2349,9 @@ void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ski
if(killerMob) {
Mob *oos = killerMob->GetOwnerOrSelf();
parse->EventNPC(EVENT_DEATH, this, oos, "", 0);
mod_npc_killed(oos);
uint16 emoteid = this->GetEmoteID();
if(emoteid != 0)
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.
// 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())
{

View File

@ -166,7 +166,8 @@ Client::Client(EQStreamInterface* ieqs)
qglobal_purge_timer(30000),
TrackingTimer(2000),
RespawnFromHoverTimer(0),
merc_timer(RuleI(Mercs, UpkeepIntervalMS))
merc_timer(RuleI(Mercs, UpkeepIntervalMS)),
ItemTickTimer(10000)
{
for(int cf=0; cf < _FilterCount; cf++)
ClientFilters[cf] = FilterShow;
@ -325,6 +326,7 @@ Client::Client(EQStreamInterface* ieqs)
}
MaxXTargets = 5;
XTargetAutoAddHaters = true;
LoadAccountFlags();
}
Client::~Client() {
@ -842,6 +844,9 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
safe_delete(pack);
}
//Return true to proceed, false to return
if(!mod_client_message(message, chan_num)) { return; }
// Garble the message based on drunkness
if (m_pp.intoxication > 0) {
GarbleMessage(message, (int)(m_pp.intoxication / 3));
@ -2334,10 +2339,11 @@ bool Client::CheckIncreaseSkill(SkillType skillid, Mob *against_who, int chancem
if(against_who)
{
if(against_who->SpecAttacks[IMMUNE_AGGRO] || against_who->IsClient() ||
GetLevelCon(against_who->GetLevel()) == CON_GREEN)
if( against_who->SpecAttacks[IMMUNE_AGGRO] || against_who->IsClient() ||
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)
Chance = 1; // Make it always possible
Chance = (Chance * RuleI(Character, SkillUpModifier) / 100);
Chance = mod_increase_skill_chance(Chance, against_who);
if(MakeRandomFloat(0, 99) < Chance)
{
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 = mod_bindwound_percent(max_percent, bindmob);
int max_hp = bindmob->GetMaxHP()*max_percent/100;
// send bindmob new hp's
@ -2736,6 +2747,8 @@ bool Client::BindWound(Mob* bindmob, bool start, bool fail){
bindhps += bindhps*bindBonus / 100;
bindhps = mod_bindwound_hp(bindhps, bindmob);
//if the bind takes them above the max bindable
//cap it at that value. Dont know if live does it this way
//but it makes sense to me.
@ -7622,3 +7635,110 @@ some day.
}
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 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:
friend class Mob;
void CalcItemBonuses(StatBonuses* newbon);
@ -1435,8 +1464,10 @@ private:
uint8 MaxXTargets;
bool XTargetAutoAddHaters;
struct XTarget_Struct XTargets[XTARGET_HARDCAP];
struct XTarget_Struct XTargets[XTARGET_HARDCAP];
Timer ItemTickTimer;
std::map<std::string,std::string> accountflags;
};
#include "parser.h"

View File

@ -1415,6 +1415,8 @@ int Client::CalcHaste() {
h += spellbonuses.hastetype3;
h += ExtraHaste; //GM granted haste.
h = mod_client_haste(h);
if (spellbonuses.inhibitmelee){
if (h >= 0)
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))
{
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;
}
}
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
{
if(GetClientVersion() >= EQClientSoD && !inst->IsEquipable(GetBaseRace(),GetClass()))
@ -2605,6 +2661,8 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app)
con->faction = FACTION_DUBIOUS;
}
mod_consider(tmob, con);
QueuePacket(outapp);
safe_delete(outapp);
return;
@ -3200,6 +3258,8 @@ void Client::Handle_OP_ItemLinkClick(const EQApplicationPacket *app)
if((response).size() > 0)
{
if( !mod_saylink(response, silentsaylink) ) { return; }
if(this->GetTarget() && this->GetTarget()->IsNPC())
{
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 (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) "
" 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 "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
" 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 (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 "
" 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 "
" HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
" LIMIT 200 "
@ -7714,7 +7774,11 @@ void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app)
SetHP(GetHP() - damage);
if(GetHP() <= 0)
{
mod_client_death_env();
Death(0, 32000, SPELL_UNKNOWN, HAND_TO_HAND);
}
SendHPUpdate();
return;
}

View File

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

View File

@ -88,7 +88,10 @@ const char *QuestEventSubroutines[_LargestEventID] = {
"EVENT_CLICK_OBJECT",
"EVENT_DISCOVER_ITEM",
"EVENT_DISCONNECT",
"EVENT_CONNECT"
"EVENT_CONNECT",
"EVENT_ITEM_TICK",
"EVENT_DUEL_WIN",
"EVENT_DUEL_LOSE"
};
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) {
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();
while(iterator.MoreElements()) {
Client *cur = iterator.GetData();

View File

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

View File

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

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;
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)
{
max_quiver = temp_wr;
@ -3784,12 +3784,12 @@ void Mob::InsertQuestGlobal(int charid, int npcid, int zoneid, const char *varna
char *query = 0;
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;
if (duration == INT_MAX)
{
duration_ss << "nullptr";
duration_ss << "NULL";
}
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 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:
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);

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")
{
NPCSpecialAttacks(val.c_str(), 0);
//Added reset flag.
NPCSpecialAttacks(val.c_str(), 0, 1);
return;
}
@ -2414,3 +2415,75 @@ FACTION_VALUE NPC::CheckNPCFactionAlly(int32 other_faction) {
bool NPC::IsFactionListAlly(uint32 other_faction) {
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(uint32 itemid, uint16 charges, bool equipitem = true);
void AddLootTable();
void AddLootTable(uint32 ldid);
void DescribeAggro(Client *towho, Mob *mob, bool verbose);
void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0);
void CheckMinMaxLevel(Mob *them);
@ -454,6 +454,13 @@ public:
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:
const NPCType* NPCTypedata;

View File

@ -5651,6 +5651,91 @@ XS(XS_Client_GetInstanceID)
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
extern "C"
#endif
@ -5877,6 +5962,9 @@ XS(boot_Client)
newXSproto(strcpy(buf, "AddAlternateCurrencyValue"), XS_Client_AddAlternateCurrencyValue, file, "$$$");
newXSproto(strcpy(buf, "SendWebLink"), XS_Client_SendWebLink, 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;
}

View File

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

View File

@ -143,7 +143,17 @@ XS(XS_NPC_AddLootTable)
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->AddLootTable();
uint32 loottable_id = 0;
if(items > 1)
{
loottable_id = (uint32)SvUV(ST(1));
THIS->AddLootTable(loottable_id);
}
else
{
THIS->AddLootTable();
}
}
XSRETURN_EMPTY;
}
@ -2075,6 +2085,57 @@ XS(XS_NPC_GetAccuracyRating)
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
extern "C"
@ -2174,6 +2235,8 @@ XS(boot_NPC)
newXSproto(strcpy(buf, "GetSlowMitigation"), XS_NPC_GetAttackSpeed, file, "$");
newXSproto(strcpy(buf, "GetAttackSpeed"), XS_NPC_GetSlowMitigation, 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;
}

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.
if (petpower == -1) {
if (this->IsClient())
if (this->IsClient()) {
act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);
act_power = CastToClient()->mod_pet_power(act_power, spell_id);
}
}
else if (petpower > 0)
act_power = petpower;

View File

@ -830,10 +830,11 @@ uint16 QuestManager::scribespells(uint8 max_level, uint8 min_level) {
{
if
(
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] >= min_level && //minimum level
spells[curspell].skill != 52
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] >= min_level && //minimum level
spells[curspell].skill != 52 &&
( RuleB(Spells, UseCHAScribeHack) && spells[curspell].effectid[EFFECT_COUNT - 1] != 10 )
)
{
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[initiator->GetPP().class_-1] <= max_level && //maximum 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)){
@ -1239,11 +1241,11 @@ int QuestManager::InsertQuestGlobal(
char *query = 0;
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;
if (duration == INT_MAX)
{
duration_ss << "nullptr";
duration_ss << "NULL";
}
else
{

View File

@ -253,6 +253,8 @@ public:
bool createBot(const char *name, const char *lastname, uint8 level, uint16 race, uint8 botclass, uint8 gender);
#endif
inline uint16 GetMana(uint32 spell_id) { return( spells[spell_id].mana); }
protected:
Mob *owner; //NPC is never nullptr when functions are called.
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,
uint32 respawn, uint32 variance, uint32 timeleft, uint32 grid,
uint16 in_cond_id, int16 in_min_value, bool in_enabled, EmuAppearance anim)
: timer(100000)
: timer(100000), killcount(0)
{
spawn2_id = in_spawn2_id;
spawngroup_id_ = spawngroup_id;
@ -219,6 +219,9 @@ bool Spawn2::Process() {
currentnpcid = npcid;
NPC* npc = new NPC(tmp, this, x, y, z, heading, FlyMode3);
npc->mod_prespawn(this);
npcthis = npc;
npc->AddLootTable();
npc->SetSp2(spawngroup_id_);
@ -328,7 +331,7 @@ void Spawn2::ForceDespawn()
}
//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
uint32 cur = resetTimer();
@ -338,6 +341,8 @@ void Spawn2::DeathReset()
//zero out our NPC since he is now gone
npcthis = nullptr;
if(realdeath) { killcount++; }
//if we have a valid spawn id
if(spawn2_id)
{

View File

@ -49,7 +49,7 @@ public:
void Repop(uint32 delay = 0);
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);
uint32 GetID() { return spawn2_id; }
@ -71,6 +71,7 @@ public:
bool NPCPointerValid() { return (npcthis!=nullptr); }
void SetNPCPointer(NPC* n) { npcthis = n; }
void SetTimer(uint32 duration) { timer.Start(duration); }
uint32 GetKillCount() { return killcount; }
protected:
friend class Zone;
Timer timer;
@ -92,6 +93,7 @@ private:
bool enabled;
EmuAppearance anim;
bool IsDespawned;
uint32 killcount;
};
class SpawnCondition {

View File

@ -48,6 +48,8 @@ int Mob::GetKickDamage() {
int32 mindmg = 1;
ApplySpecialAttackMod(KICK, dmg,mindmg);
dmg = mod_kick_damage(dmg);
return(dmg);
}
@ -67,6 +69,8 @@ int Mob::GetBashDamage() {
int32 mindmg = 1;
ApplySpecialAttackMod(BASH, dmg, mindmg);
dmg = mod_bash_damage(dmg);
return(dmg);
}
@ -281,6 +285,8 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) {
int32 min_dmg = 0;
DoAnim(anim2HSlashing);
max_dmg = mod_frenzy_damage(max_dmg);
if (GetLevel() < 51)
min_dmg = 1;
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);
return(reuse);
@ -663,6 +672,8 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime)
ndamage = -5;
}
ndamage = mod_backstab_damage(ndamage);
DoSpecialAttackDamage(other, BACKSTAB, ndamage, min_hit, hate, ReuseTime);
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);
bool dobonus = false;
if(GetClass() == RANGER && GetLevel() > 50)
{
if(RuleB(Combat, ArcheryBonusRequiresStationary))
int bonuschance = RuleI(Combat, ArcheryBonusChance);
bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon);
if( !RuleB(Combat, UseArcheryBonusRoll) || (MakeRandomInt(1, 100) < bonuschance) )
{
if(other->IsNPC() && !other->IsMoving() && !other->IsRooted())
if(RuleB(Combat, ArcheryBonusRequiresStationary))
{
MaxDmg *= (float)2;
hate *= (float)2;
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
if(other->IsNPC() && !other->IsMoving() && !other->IsRooted())
{
dobonus = true;
}
}
else
{
dobonus = true;
}
}
else
if(dobonus)
{
MaxDmg *= (float)2;
hate *= (float)2;
MaxDmg = mod_archery_bonus_damage(MaxDmg);
mlog(COMBAT__RANGED, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg);
Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE);
}
@ -925,6 +948,9 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
ApplyMeleeDamageBonus(ARCHERY, TotalDmg);
TotalDmg += other->GetAdditionalDamage(this, 0, true, ARCHERY);
TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(ARCHERY) / 100) + GetSkillDmgAmt(ARCHERY);
TotalDmg = mod_archery_damage(TotalDmg, dobonus);
TryCriticalHit(other, ARCHERY, TotalDmg);
other->AddToHateList(this, hate, 0, false);
}
@ -941,6 +967,12 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item
{
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)
@ -1088,6 +1120,8 @@ uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg)
if(MaxDmg < minDmg)
MaxDmg = minDmg;
MaxDmg = mod_throwing_damage(MaxDmg);
return MaxDmg;
}

View File

@ -627,7 +627,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
#endif
// EverHood
if(caster && GetPrimaryFaction()>0) {
caster->AddFactionBonus(GetPrimaryFaction(),spell.base[0]);
caster->AddFactionBonus(GetPrimaryFaction(),effect_value);
}
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);
}
effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster);
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)
{
/*
neotokyo: i need those formulas checked!!!!
0 = base
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;
if(ubase < 0)
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
// 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;
case 111:
result = updownsign * (ubase + 6 * level_diff); break;
result = updownsign * (ubase + 6 * (caster_level - GetMinLevel(spell_id))); break;
case 112:
result = updownsign * (ubase + 8 * level_diff); break;
result = updownsign * (ubase + 8 * (caster_level - GetMinLevel(spell_id))); break;
case 113:
result = updownsign * (ubase + 10 * level_diff); break;
result = updownsign * (ubase + 10 * (caster_level - GetMinLevel(spell_id))); break;
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
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
result = ubase + 8 * level_diff; break;
result = ubase + 8 * (caster_level - GetMinLevel(spell_id)); break;
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
result = ubase + 20 * level_diff; break;
result = ubase + 20 * (caster_level - GetMinLevel(spell_id)); break;
case 119: // confirmed 2/6/04
result = ubase + (caster_level / 8); break;
@ -5319,7 +5319,7 @@ int32 Mob::GetAdditionalDamage(Mob *caster, uint32 spell_id, bool use_skill, uin
int32 Mob::ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard) {
//9-17-12: This is likely causing crashes, disabled till can resolve.
// 9-17-12: This is likely causing crashes, disabled till can resolve.
if (IsBard)
return value;

View File

@ -440,6 +440,8 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot,
return(true);
}
cast_time = mod_cast_time(cast_time);
// ok we know it has a cast time so we can start the timer now
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))
&& inventory_slot != 0xFFFFFFFF) // 10 is an item
{
const ItemInst* inst = CastToClient()->GetInv()[inventory_slot];
if (inst && inst->IsType(ItemClassCommon) && (inst->GetItem()->Click.Effect == spell_id) && inst->GetCharges())
bool fromaug = false;
const ItemInst* inst = CastToClient()->GetInv()[inventory_slot];
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();
int16 charges = inst->GetItem()->MaxCharges;
if(fromaug) { charges = -1; } //Don't destroy the parent item
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);
DeleteChargeFromSlot = inventory_slot;
@ -2328,6 +2372,8 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste
IsBlindSpell(spell_id))
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",
spell_id, castlevel, formula, duration, res);
@ -2431,6 +2477,9 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
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
switch(spellid1)
{
@ -4019,40 +4068,67 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
resist_chance = 0;
}
//Adjust our resist chance based on level modifiers
int temp_level_diff = GetLevel() - caster->GetLevel();
if(IsNPC() && GetLevel() >= RuleI(Casting, ResistFalloff))
{
int a = (RuleI(Casting, ResistFalloff) - 1) - caster->GetLevel();
if(a > 0)
{
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
//Adjust our resist chance based on level modifiers
int temp_level_diff = GetLevel() - caster->GetLevel();
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff))
{
int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(a > 0)
{
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
temp_level_diff = 15;
}
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
temp_level_diff = 15;
}
if(IsNPC() && temp_level_diff < -9)
{
temp_level_diff = -9;
}
if(IsNPC() && temp_level_diff < -9)
{
temp_level_diff = -9;
}
int level_mod = temp_level_diff * temp_level_diff / 2;
if(temp_level_diff < 0)
{
level_mod = -level_mod;
}
int level_mod = temp_level_diff * temp_level_diff / 2;
if(temp_level_diff < 0)
{
level_mod = -level_mod;
}
if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20)
if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20)
{
level_mod = 1000;
}
//Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
{
int level_diff;
if(GetLevel() >= RuleI(Casting,ResistFalloff))
{
level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(level_diff < 0)
{
level_diff = 0;
}
}
else
{
level_diff = GetLevel() - caster->GetLevel();
}
level_mod += (2 * level_diff);
}
if (CharismaCheck)
{
level_mod = 1000;
//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
@ -4060,32 +4136,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
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
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
{
int level_diff;
if(GetLevel() >= RuleI(Casting, ResistFalloff))
{
level_diff = (RuleI(Casting, ResistFalloff) - 1) - caster->GetLevel();
if(level_diff < 0)
{
level_diff = 0;
}
}
else
{
level_diff = GetLevel() - caster->GetLevel();
}
resist_chance += (2 * level_diff);
}
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.
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;
}
// 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
// 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
// strings from the first activity in the task, however the task chooser/selector
// does not appear to make use of them.
sprintf(Ptr, "nullptr");
sprintf(Ptr, "ABCD");
Ptr = Ptr + strlen(Ptr) + 1;
sprintf(Ptr, "nullptr");
sprintf(Ptr, "ABCD");
Ptr = Ptr + strlen(Ptr) + 1;
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
// appear anywhere in the client window.
sprintf(Ptr, "nullptr");
sprintf(Ptr, "ABCD");
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){
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)) {
//Only if stage1 and stage2 succeeded you get a skillup.
SetSkill(tradeskill, current_raw_skill + 1);

View File

@ -251,6 +251,9 @@ int32 Client::TributeItem(uint32 slot, uint32 quantity) {
//figure out what its worth
int32 pts = inst->GetItem()->Favor;
pts = mod_tribute_item_value(pts);
if(pts < 1) {
Message(13, "This item is worthless for favor.");
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))),
clientauth_timer(AUTHENTICATION_TIMEOUT * 1000),
spawn2_timer(1000),
qglobal_purge_timer(30000)
qglobal_purge_timer(30000),
hotzone_timer(120000)
{
zoneid = in_zoneid;
instanceid = in_instanceid;
@ -1164,6 +1165,12 @@ bool Zone::Init(bool iStaticZone) {
zone->zone_time.setEQTimeZone(database.GetZoneTZ(zoneid, GetInstanceVersion()));
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;
}
@ -1483,6 +1490,8 @@ bool Zone::Process() {
}
}
if(hotzone_timer.Check()) { UpdateHotzone(); }
return true;
}
@ -1531,6 +1540,9 @@ void Zone::Repop(uint32 delay) {
MZoneLock.unlock();
initgrids_timer.Start();
//MODDING HOOK FOR REPOP
mod_repop();
}
void Zone::GetTimeSync()
@ -2657,3 +2669,87 @@ void Zone::ReloadWorld(uint32 Option){
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 "pathing.h"
#include "QGlobals.h"
#include <unordered_map>
class Map;
class WaterMap;
@ -69,6 +70,14 @@ struct ZoneEXPModInfo {
float AAExpMod;
};
struct item_tick_struct {
uint32 itemid;
uint32 chance;
uint32 level;
int16 bagslot;
std::string qglobal;
};
extern EntityList entity_list;
class database;
class PathManager;
@ -253,6 +262,15 @@ public:
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:
uint32 zoneid;
uint32 instanceid;
@ -305,6 +323,7 @@ private:
LinkedList<ZoneClientAuth_Struct*> client_auth_list;
QGlobalCache *qGlobals;
Timer hotzone_timer;
Mutex MZoneLock;
};