Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Arthur Dene Ice 2014-05-05 16:26:40 -07:00
commit 2d6670cfe5
87 changed files with 3518 additions and 2512 deletions

View File

@ -99,6 +99,10 @@ IF(UNIX)
ADD_DEFINITIONS(-DFREEBSD)
SET(FREEBSD TRUE)
ENDIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
IF(CMAKE_SYSTEM_NAME MATCHES "Darwin")
ADD_DEFINITIONS(-DDARWIN)
SET(DARWIN TRUE)
ENDIF(CMAKE_SYSTEM_NAME MATCHES "Darwin")
ENDIF(UNIX)
#use stdint.h types if they exist for this platform (we have to guess otherwise)
@ -118,6 +122,78 @@ SET(EQEMU_DEBUG_LEVEL 5 CACHE STRING "EQEmu debug level:
10 - More errors than you ever wanted to see"
)
SET(EQEMU_LOG_LEVEL_STATUS 2 CACHE STRING "EQEmu logging level for [Status]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_NORMAL 3 CACHE STRING "EQEmu logging level for [Normal]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_ERROR 2 CACHE STRING "EQEmu logging level for [Error]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_DEBUG 3 CACHE STRING "EQEmu logging level for [Debug]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_QUEST 2 CACHE STRING "EQEmu logging level for [Quest]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_COMMANDS 1 CACHE STRING "EQEmu logging level for [Commands]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
SET(EQEMU_LOG_LEVEL_CRASH 3 CACHE STRING "EQEmu logging level for [Crash]:
0 - Disabled
1 - Ouput to File Enabled
2 - Output to stdout Enabled
3 - Output to File and stdout Enabled
8 - Output to stderr Enabled
9 - Output to File and stderr Enabled
11 - Output to File, stdout and stderr Enabled"
)
MARK_AS_ADVANCED(EQEMU_LOG_LEVEL_STATUS EQEMU_LOG_LEVEL_NORMAL EQEMU_LOG_LEVEL_ERROR EQEMU_LOG_LEVEL_DEBUG EQEMU_LOG_LEVEL_QUEST EQEMU_LOG_LEVEL_COMMANDS EQEMU_LOG_LEVEL_CRASH)
SET(EQEMU_STREAM_SEND_RATE 1048576 CACHE STRING "Advanced: Base amount of data stream can send before throttle.")
SET(EQEMU_STREAM_DECAY_RATE 78642 CACHE STRING "Advanced: Base amount of data stream recovers per tic.")
SET(EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL 3.0 CACHE STRING "Advanced: Multiplier on retransmit timeout.")
@ -182,6 +258,14 @@ ADD_DEFINITIONS(-DDECAYBASE=${EQEMU_STREAM_DECAY_RATE})
ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MULT=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MUL})
ADD_DEFINITIONS(-DRETRANSMIT_TIMEOUT_MAX=${EQEMU_STREAM_RETRANSMIT_TIMEOUT_MAX})
ADD_DEFINITIONS(-DAVERAGE_DELTA_MAX=${EQEMU_STREAM_AVERAGE_DELTA_MAX})
ADD_DEFINITIONS(-DLOG_LEVEL_STATUS=${EQEMU_LOG_LEVEL_STATUS})
ADD_DEFINITIONS(-DLOG_LEVEL_NORMAL=${EQEMU_LOG_LEVEL_NORMAL})
ADD_DEFINITIONS(-DLOG_LEVEL_ERROR=${EQEMU_LOG_LEVEL_ERROR})
ADD_DEFINITIONS(-DLOG_LEVEL_DEBUG=${EQEMU_LOG_LEVEL_DEBUG})
ADD_DEFINITIONS(-DLOG_LEVEL_QUEST=${EQEMU_LOG_LEVEL_QUEST})
ADD_DEFINITIONS(-DLOG_LEVEL_COMMANDS=${EQEMU_LOG_LEVEL_COMMANDS})
ADD_DEFINITIONS(-DLOG_LEVEL_CRASH=${EQEMU_LOG_LEVEL_CRASH})
IF(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS)
ADD_DEFINITIONS(-DRETRANSMIT_ACKED_PACKETS=true)
ELSE(EQEMU_STREAM_RETRANSMIT_ACKED_PACKETS)

View File

@ -1,5 +1,143 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50)
-------------------------------------------------------
== 04/29/2014 ==
KLS: Implemented new map code based on some of Derision's earlier work. Old maps still work with this system and don't need to be regenerated. We're still working on a new azone solution for better/more efficient maps.
== 04/27/2014 ==
Kayen: Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'.
Implemented new field in 'npc_spell_effects_id' in npc_types.
These are used to directly apply spell effect bonuses to NPC's without requirings spells/buffs.
Example: Allow an npc to spawn with an innate 50 pt damage shield and a 5% chance to critical hit.
Please see the wiki page: http://wiki.eqemulator.org/p?npc_spell_effects_entries for details.
*NPC's can now do critical heals / damage spells if bonus is applied from table.
Required SQL: utils/sql/git/required/2014_04_27_AISpellEffects.sql
Note: 30 examples of spell effects have been included by default in this sql. Edited/removed as needed.
== 04/25/2014 ==
cavedude: Corrected a crash in spawn_conditions caused by NPCs on a one way path.
cavedude: Added strict column to spawn_events which will prevent an event from enabling if it's mid-cycle.
cavedude: Prevented disabled or strict spawn_events from enabling when the zone first boots.
cavedude: Fixed the quest function toggle_spawn_event under Perl.
If you're using the quest function toggle_spawn_event (worked on Lua only) it has changed syntax to:
toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base)
Required SQL: utils/sql/git/required/2014_04_25_spawn_events.sql
== 04/23/2014 ==
Kayen: Improved SE_LimitCombatSkills will now more accurately determine if a spell is a combat proc.
Kayen: SE_LimitInstant will now also work when set to include instant spells.
Optional SQL: utils/sql/git/optional/2014_04_23_FocusComabtProcs.sql
Note: Set to false, if enabled will allow all combat procs to receive spell focuses.
== 04/21/2014 ==
Secrets: Crash fix for more hatelist crashes.
Secrets: Hate list fixes, again.
Secrets: Revert of hatelist changes.
== 04/20/2014 ==
Secrets: Changed the functionality of EQDEBUG cmake flag. It now suppresses logs, but shows crashes in any scenario when set to 1. It will also now show crashes even if the log system is disabled.
KLS: Change to how quest signals work, signals with no delay will now be added to the signal queue. This addresses an odd timing issue where NPCs are in a state of life/death flux when a signal from event_death goes off.
KLS: Added cmake flags to define how logging behavior works for each different log type.
Secrets: Crash fix for Hatelist crash observed
== 04/18/2014 ==
Akkadius: Added #command error message suppression for those who don't want to see 'Command is not recognized' constantly
- You need to have rule 'Chat:SuppressCommandErrors' set to true, this is set to false by default
- Required SQL: 2014_04_18_Suppress_Command_Error.sql
== 04/15/2014 ==
Akkadius: Exported $client->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg) - Will be available for simple plugin use
Akkadius: Exported $client->ExpeditionMessage(THIS, ExpdID, Message) - In use with custom expedition mod that will be released soon
== 04/12/2014 ==
Kayen: Fixed an with the slow mitigation code that would cause it to spam the message. Optimized the way the variable is handled for slow mitigation.
Required SQL: utils/sql/git/required/2014_04_12_SlowMitigation.sql
Note: This changes the variable type in the sql table from FLOAT to INT and updates your database.
(When setting slow mitigation 50 = 50%, 100 = 100% ect. You can also set > 100 which will cause slow to become haste now with appropriate in game msg given)
== 04/10/2014 ==
Kayen: Added 'no_target_hotkey' field to npc_types table. This will prevent the NPC from being targeted with F8 (Warning: Will also turn it's name YELLOW)
Kayen: Added a rule to make all (Player cast) Swarm Pets not targetable with F8 (nearest NPC) by default (Warning: Will also turn pets names YELLOW). This is semi-hack but it works.
Kayen: Player cast swarm pets can now be healed and buffed consistent with live.
Optional SQL: utils/sql/git/optional/2014_04_10_SwarmPetNotTargetableWithHotKey.sql
Required SQL: utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql
Note: For the required new npc_types field you DO NOT need to set values for swarm pets if you enable the above rule.
== 04/09/2014 ==
Kayen: Implemented ability to use the actual live spell projectile graphics that are defined in the modern spell file.
*This is disabled by default. Recommend enabling if your server uses an UF+ spell file AND most of your players use UF+ clients.
Kayen: Expanded the PERL ProjectileAnim(mob, item_id, [IsArrow?, speed, angle, tilt, arc, IDFile]) so you can now just set the weapon model graphic IT####
Example: ProjectileAnim($npc, 0, 0, 1, 0, 0, 0, "IT10747") This will shoot an SK 2.0 sword.
Kayen: Updated wizard innate critical damage modifier to be from 20-70% of base damage (based on live parses)
Optional SQL: utils/sql/git/optional/2014_04_09_SpellProjectileRule.sql
Note: This sql also contains a query to check if your spell file is compatible.
== 04/06/2014 ==
Uleat: Changed Mob::CanThisClassDualWield() behavior. This should let non-monk/beastlord dual-wielding classes attack with either fist as long as the other hand is occupied.
Notes:
See this thread for more information and to provide feedback: http://www.eqemulator.org/forums/showthread.php?p=229328#post229328
== 04/05/2014 ==
Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality
for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality.
This rule is set to 'true' by default as the original functionality from Live was intended to be
Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can
see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading
variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and
unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval.
Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true.
If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not.
This is for some servers that may want to have as many procs triggering from weapons as possible in a single round.
Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql
== 04/04/2014 ==
Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing.
SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations.
Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql
== 04/03/2014 ==
Kayen: Implemented live like spell projectiles (ie. Mage Bolts).
Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql
Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients.
== 04/01/2014 ==
demonstar55: Implemented ability for a merchant to open and close shop.
Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop()
GM Commands: #merchant_open_shop (short: #open_shop) and #merchant_close_shop (short: #close_shop)
default to status 100, just in case you need to force the merchants status
Trevius: Fixed potential endless quest loop with EVENT_COMBAT and WipeHateList().
== 03/31/2014 ==
Uleat: Fix for unconscious skillups.
Uleat: Fix for crash issue with nullptr reference in recent Client::SummonItem() work.
Uleat: Added rule for GM Status check code in Client::SummonItem().
Note: Rule default is set to 250..but, implementation is on hold until load item code handles the database 'minstatus' field.
Uleat: Added RuleB(Bots, BotLevelsWithOwner). Bots will auto-update as their owner levels/de-levels. Appearance packets are sent to show the 'leveling effect' as well as updating client entities.
Trevius: Prevented an endless loop crash related to EVENT_TASK_STAGE_COMPLETE.
Optional Bot SQL: utils/sql/git/bot/optional/2014_03_31_BotLevelsWithOwnerRule.sql
Note: This sql is required to activate the optional behavior.
== 03/27/2014 ==
Kayen: SE_Gate will now use have a fail chance as defined by its base value in the spell data.
Kayen: SE_Succor will now have a baseline fail chance of (2%). Rule added to adjust this as needed.
Kayen: SE_FeignDeath will now have a fail chance as defined by its base value in the spell data.
Optional SQL: utils/sql/git/optional/2014_03_27_SuccorFailRule.sql
== 03/22/2014 ==
Uleat: Moved the existing 'load_bots' and 'drop_bots' sqls into the emu git repository for the time being. Look to the
/utils/sql/git/bots/ folder to find them. The 'load_bots' sql has been updated to include the below fix, as well as

View File

@ -26,7 +26,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(export_client_files "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(export_client_files "z")
TARGET_LINK_LIBRARIES(export_client_files "m")
TARGET_LINK_LIBRARIES(export_client_files "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(export_client_files "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(export_client_files "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -26,7 +26,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(import_client_files "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(import_client_files "z")
TARGET_LINK_LIBRARIES(import_client_files "m")
TARGET_LINK_LIBRARIES(import_client_files "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(import_client_files "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(import_client_files "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -30,6 +30,10 @@
#ifdef FREEBSD //Timothy Whitman - January 7, 2003
#define MSG_NOSIGNAL 0
#endif
#ifdef DARWIN
#define MSG_NOSIGNAL SO_NOSIGPIPE // Corysia Taware - Sept. 27, 2013
// See http://lists.apple.com/archives/macnetworkprog/2002/Dec/msg00091.html
#endif // DARWIN
#ifdef _WINDOWS
InitWinsock winsock;

View File

@ -97,8 +97,12 @@ void BaseTCPServer::ListenNewConnections() {
from.sin_family = AF_INET;
fromlen = sizeof(from);
LockMutex lock(&MSock);
#ifndef DARWIN // Corysia - On OSX, 0 is a valid fd.
if (!sock)
return;
#else
if (sock == -1) return;
#endif
// Check for pending connects
#ifdef _WINDOWS

View File

@ -35,29 +35,20 @@ static const char* FileNames[EQEMuLog::MaxLogID] = { "logs/eqemu", "logs/eqemu",
static const char* LogNames[EQEMuLog::MaxLogID] = { "Status", "Normal", "Error", "Debug", "Quest", "Command", "Crash" };
EQEMuLog::EQEMuLog() {
// MOpen = new Mutex;
// MLog = new Mutex*[MaxLogID];
// fp = new FILE*[MaxLogID];
// pLogStatus = new uint8[MaxLogID];
for (int i=0; i<MaxLogID; i++) {
fp[i] = 0;
// MLog[i] = new Mutex;
#if EQDEBUG >= 2
pLogStatus[i] = 1 | 2;
#else
pLogStatus[i] = 0;
#endif
logCallbackFmt[i] = nullptr;
logCallbackBuf[i] = nullptr;
logCallbackPva[i] = nullptr;
}
// TODO: Make this read from an ini or something, everyone has different opinions on what it should be
#if EQDEBUG < 2
pLogStatus[Status] = 2;
pLogStatus[Error] = 2;
pLogStatus[Quest] = 2;
pLogStatus[Commands] = 1;
#endif
pLogStatus[Status] = LOG_LEVEL_STATUS;
pLogStatus[Normal] = LOG_LEVEL_NORMAL;
pLogStatus[Error] = LOG_LEVEL_ERROR;
pLogStatus[Debug] = LOG_LEVEL_DEBUG;
pLogStatus[Quest] = LOG_LEVEL_QUEST;
pLogStatus[Commands] = LOG_LEVEL_COMMANDS;
pLogStatus[Crash] = LOG_LEVEL_CRASH;
logFileValid = true;
}
@ -68,10 +59,6 @@ EQEMuLog::~EQEMuLog() {
if (fp[i])
fclose(fp[i]);
}
// safe_delete_array(fp);
// safe_delete_array(MLog);
// safe_delete_array(pLogStatus);
// safe_delete(MOpen);
}
bool EQEMuLog::open(LogIDs id) {

View File

@ -55,9 +55,21 @@ namespace EQEmu {
std::string final_name = name;
final_name += ".lock";
#ifdef __DARWIN
#if __DARWIN_C_LEVEL < 200809L
imp_->fd_ = open(final_name.c_str(),
O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR);
#else
imp_->fd_ = open(final_name.c_str(),
O_RDWR | O_CREAT | O_CLOEXEC,
S_IRUSR | S_IWUSR);
#endif
#else
imp_->fd_ = open(final_name.c_str(),
O_RDWR | O_CREAT | O_CLOEXEC,
S_IRUSR | S_IWUSR);
#endif
if(imp_->fd_ == -1) {
EQ_EXCEPT("IPC Mutex", "Could not create mutex.");

View File

@ -135,9 +135,11 @@ RULE_CATEGORY_END()
RULE_CATEGORY( Pets )
RULE_REAL( Pets, AttackCommandRange, 150 )
RULE_BOOL( Pets, UnTargetableSwarmPet, false )
RULE_BOOL( Pets, SwarmPetNotTargetableWithHotKey, false ) //On SOF+ clients this a semi-hack to make swarm pets not F8 targetable.
RULE_CATEGORY_END()
RULE_CATEGORY( GM )
RULE_INT ( GM, MinStatusToSummonItem, 250)
RULE_INT ( GM, MinStatusToZoneAnywhere, 250 )
RULE_CATEGORY_END()
@ -302,6 +304,12 @@ RULE_BOOL ( Spells, UseCHAScribeHack, false) //ScribeSpells and TrainDiscs quest
RULE_BOOL ( Spells, BuffLevelRestrictions, true) //Buffs will not land on low level toons like live
RULE_INT ( Spells, RootBreakCheckChance, 70) //Determines chance for a root break check to occur each buff tick.
RULE_INT ( Spells, FearBreakCheckChance, 70) //Determines chance for a fear break check to occur each buff tick.
RULE_INT ( Spells, SuccorFailChance, 2) //Determines chance for a succor spell not to teleport an invidual player
RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'.
RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'.
RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'.
RULE_BOOL ( Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file.
RULE_BOOL ( Spells, FocusCombatProcs, false) //Allow all combat procs to receive focus effects.
RULE_CATEGORY_END()
RULE_CATEGORY( Combat )
@ -391,6 +399,7 @@ RULE_BOOL ( Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus req
RULE_INT ( Combat, ArcheryBonusChance, 50)
RULE_INT ( Combat, BerserkerFrenzyStart, 35)
RULE_INT ( Combat, BerserkerFrenzyEnd, 45)
RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round
RULE_CATEGORY_END()
RULE_CATEGORY( NPC )
@ -446,6 +455,7 @@ RULE_BOOL ( Bots, BotSpellQuest, false ) // Anita Thrall's (Anita_Thrall.pl) Bot
RULE_INT ( Bots, BotAAExpansion, 8 ) // Bots get AAs through this expansion
RULE_BOOL ( Bots, BotGroupXP, false ) // Determines whether client gets xp for bots outside their group.
RULE_BOOL ( Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs.
RULE_BOOL ( Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior)
RULE_CATEGORY_END()
#endif
@ -455,6 +465,7 @@ RULE_BOOL ( Chat, ServerWideAuction, true)
RULE_BOOL ( Chat, EnableVoiceMacros, true)
RULE_BOOL ( Chat, EnableMailKeyIPVerification, true)
RULE_BOOL ( Chat, EnableAntiSpam, true)
RULE_BOOL ( Chat, SuppressCommandErrors, false) // Do not suppress by default
RULE_INT ( Chat, MinStatusToBypassAntiSpam, 100)
RULE_INT ( Chat, MinimumMessagesPerInterval, 4)
RULE_INT ( Chat, MaximumMessagesPerInterval, 12)

View File

@ -677,11 +677,9 @@ bool IsCombatSkill(uint16 spell_id)
{
if (!IsValidSpell(spell_id))
return false;
//Check if Discipline OR melee proc (from non-castable spell)
if ((spells[spell_id].mana == 0 &&
(spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep)) ||
((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0)))
//Check if Discipline
if ((spells[spell_id].mana == 0 && (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep)))
return true;
return false;

View File

@ -159,8 +159,8 @@ typedef enum {
#define SE_WIS 9 // implemented
#define SE_CHA 10 // implemented - used as a spacer
#define SE_AttackSpeed 11 // implemented
#define SE_Invisibility 12 // implemented
#define SE_SeeInvis 13 // implemented
#define SE_Invisibility 12 // implemented - TO DO: Implemented Invisiblity Levels
#define SE_SeeInvis 13 // implemented - TO DO: Implemented See Invisiblity Levels
#define SE_WaterBreathing 14 // implemented
#define SE_CurrentMana 15 // implemented
//#define SE_NPCFrenzy 16 // not used
@ -172,7 +172,7 @@ typedef enum {
#define SE_Charm 22 // implemented
#define SE_Fear 23 // implemented
#define SE_Stamina 24 // implemented - Invigor and such
#define SE_BindAffinity 25 // implemented
#define SE_BindAffinity 25 // implemented - TO DO: Implement 2nd and 3rd Recall (value 2,3 ect). Sets additional bind points.
#define SE_Gate 26 // implemented - Gate to bind point
#define SE_CancelMagic 27 // implemented
#define SE_InvisVsUndead 28 // implemented
@ -211,7 +211,7 @@ typedef enum {
#define SE_Identify 61 // implemented
//#define SE_ItemID 62 // not used
#define SE_WipeHateList 63 // implemented
#define SE_SpinTarget 64 // implemented
#define SE_SpinTarget 64 // implemented - TO DO: Not sure stun portion is working correctly
#define SE_InfraVision 65 // implemented
#define SE_UltraVision 66 // implemented
#define SE_EyeOfZomm 67 // implemented
@ -241,7 +241,7 @@ typedef enum {
#define SE_SummonCorpse 91 // implemented
#define SE_InstantHate 92 // implemented - add hate
#define SE_StopRain 93 // implemented - Wake of Karana
#define SE_NegateIfCombat 94 // *not implemented? - Works client side but there is comment todo in spell effects...Component of Spirit of Scale
#define SE_NegateIfCombat 94 // implemented
#define SE_Sacrifice 95 // implemented
#define SE_Silence 96 // implemented
#define SE_ManaPool 97 // implemented
@ -258,7 +258,7 @@ typedef enum {
#define SE_Familiar 108 // implemented
#define SE_SummonItemIntoBag 109 // implemented - summons stuff into container
//#define SE_IncreaseArchery 110 // not used
#define SE_ResistAll 111 // implemented
#define SE_ResistAll 111 // implemented - Note: Physical Resists are not modified by this effect.
#define SE_CastingLevel 112 // implemented
#define SE_SummonHorse 113 // implemented
#define SE_ChangeAggro 114 // implemented - Hate modifing buffs(ie horrifying visage)
@ -270,7 +270,7 @@ typedef enum {
#define SE_HealRate 120 // implemented - reduces healing by a %
#define SE_ReverseDS 121 // implemented
//#define SE_ReduceSkill 122 // not used
#define SE_Screech 123 // implemented? Spell Blocker(can only have one buff with this effect at one time)
#define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1)
#define SE_ImprovedDamage 124 // implemented
#define SE_ImprovedHeal 125 // implemented
#define SE_SpellResistReduction 126 // implemented
@ -456,7 +456,7 @@ typedef enum {
//#define SE_ArmyOfTheDead 306 // *not implemented NecroAA - This ability calls up to five shades of nearby corpses back to life to serve the necromancer. The soulless abominations will mindlessly fight the target until called back to the afterlife some time later. The first rank summons up to three shades that serve for 60 seconds, and each additional rank adds one more possible shade and increases their duration by 15 seconds
//#define SE_Appraisal 307 // *not implemented Rogue AA - This ability allows you to estimate the selling price of an item you are holding on your cursor.
#define SE_SuspendMinion 308 // not implemented as bonus
#define SE_YetAnotherGate 309 // implemented
#define SE_GateCastersBindpoint 309 // implemented - Gate to casters bind point
#define SE_ReduceReuseTimer 310 // implemented
#define SE_LimitCombatSkills 311 // implemented - Excludes focus from procs (except if proc is a memorizable spell)
//#define SE_Sanctuary 312 // *not implemented

View File

@ -30,7 +30,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(eqlaunch "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(eqlaunch "z")
TARGET_LINK_LIBRARIES(eqlaunch "m")
TARGET_LINK_LIBRARIES(eqlaunch "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(eqlaunch "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(eqlaunch "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -58,7 +58,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(loginserver "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(loginserver "z")
TARGET_LINK_LIBRARIES(loginserver "m")
TARGET_LINK_LIBRARIES(loginserver "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(loginserver "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(loginserver "pthread")
TARGET_LINK_LIBRARIES(loginserver "EQEmuAuthCrypto")
TARGET_LINK_LIBRARIES(loginserver "cryptopp")

View File

@ -36,7 +36,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(queryserv "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(queryserv "z")
TARGET_LINK_LIBRARIES(queryserv "m")
TARGET_LINK_LIBRARIES(queryserv "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(queryserv "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(queryserv "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -38,7 +38,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(shared_memory "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(shared_memory "z")
TARGET_LINK_LIBRARIES(shared_memory "m")
TARGET_LINK_LIBRARIES(shared_memory "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(shared_memory "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(shared_memory "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -32,7 +32,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(tests "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(tests "z")
TARGET_LINK_LIBRARIES(tests "m")
TARGET_LINK_LIBRARIES(tests "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(tests "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(tests "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -38,7 +38,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(ucs "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(ucs "z")
TARGET_LINK_LIBRARIES(ucs "m")
TARGET_LINK_LIBRARIES(ucs "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(ucs "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(ucs "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -0,0 +1,4 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (2, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (4, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (10, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.');

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:SuccorFailChance', '2', 'Determines chance for a succor spell not to teleport an invidual player.');

View File

@ -0,0 +1,3 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_Titanium', '1113', 'Item id for Titanium clients for Fire spell projectile.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_SOF', '80684', 'Item id for SOF clients for Fire spell projectile.');
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_NPC', '80684', 'Item id for NPC to use for Fire spell projectile.');

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:OneProcPerWeapon', 'true', 'If OneProcPerWeapon is not enabled, we reset the proc try for that weapon regardless of if we procced or not.');

View File

@ -0,0 +1,7 @@
-- Recommend enabling if your server uses an UF+ spell file and your players use UF+ client. This will give the proper graphics for all spell projectiles.
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:UseLiveSpellProjectileGFX', false, ' Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file.');
-- Use this query to check if your spell file is compatible
-- If it returns in the player_1 field IT##### it will work.
SELECT id,name,player_1 from spells_new WHERE targettype = 1;

View File

@ -0,0 +1,4 @@
INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Pets:SwarmPetNotTargetableWithHotKey', 'false', ' On SOF+ clients this a semi-hack to make swarm pets not F8 targetable. Warning: Turns pet names Yellow');

View File

@ -0,0 +1,7 @@
ALTER TABLE `npc_types` ADD `PhR` smallint( 5 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `Corrup`;
-- Approximate baseline live npc values based on extensive parsing.
UPDATE npc_types SET PhR = 10 WHERE PhR = 0 AND level <= 50;
UPDATE npc_types SET PhR = (10 + (level - 50)) WHERE PhR = 0 AND (level > 50 AND level <= 60);
UPDATE npc_types SET PhR = (20 + ((level - 60)*4)) WHERE PhR = 0 AND level > 60;

View File

@ -0,0 +1,3 @@
ALTER TABLE `npc_types` ADD `no_target_hotkey` tinyint( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `healscale`;

View File

@ -0,0 +1,7 @@
-- Convert all values from FLOAT to INT
UPDATE npc_types SET slow_mitigation = slow_mitigation * 100;
-- Change variable type from FLOAT TO INT
ALTER TABLE npc_types MODIFY slow_mitigation smallint(4) NOT NULL DEFAULT '0';

View File

@ -0,0 +1 @@
INSERT INTO `rule_values` VALUES ('0', 'Chat:SuppressCommandErrors', 'true', 'This will suppress "Command is not recognized"');

View File

@ -0,0 +1 @@
alter table spawn_events add column `strict` tinyint(4) not null default 0;

View File

@ -0,0 +1,104 @@
-- Note: The data entered into the new table are only examples and can be deleted/modified as needed.
ALTER TABLE `npc_types` ADD `npc_spells_effects_id` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells_id`;
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `npc_spells_effects`
-- ----------------------------
DROP TABLE IF EXISTS `npc_spells_effects`;
CREATE TABLE `npc_spells_effects` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` tinytext,
`parent_list` int(11) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1080 DEFAULT CHARSET=latin1;
-- ----------------------------
-- Records of npc_spells_effects
-- ----------------------------
INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical Melee [All Skills]', '0');
INSERT INTO `npc_spells_effects` VALUES ('2', 'Damage Shield', '0');
INSERT INTO `npc_spells_effects` VALUES ('3', 'Melee Haste', '0');
INSERT INTO `npc_spells_effects` VALUES ('4', 'Resist Spell Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('5', 'Resist Direct Dmg Spell Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('6', 'Reflect Spell Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('7', 'Spell Damage Shield', '0');
INSERT INTO `npc_spells_effects` VALUES ('8', 'Melee Mitigation [All]', '0');
INSERT INTO `npc_spells_effects` VALUES ('9', 'Avoid Melee', '0');
INSERT INTO `npc_spells_effects` VALUES ('10', 'Riposte Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('11', 'Dodge Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('12', 'Parry Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('13', 'Decrease Dmg Taken [2HS]', '0');
INSERT INTO `npc_spells_effects` VALUES ('14', 'Increase Dmg Taken [1HS]', '0');
INSERT INTO `npc_spells_effects` VALUES ('15', 'Block Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('16', 'Melee Lifetap', '0');
INSERT INTO `npc_spells_effects` VALUES ('17', 'Hit Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('18', 'Increase Dmg [1HS]', '0');
INSERT INTO `npc_spells_effects` VALUES ('19', 'Increase Archery Dmg', '0');
INSERT INTO `npc_spells_effects` VALUES ('20', 'Flurry Chance', '0');
INSERT INTO `npc_spells_effects` VALUES ('21', 'Add Damage [2HS]', '0');
INSERT INTO `npc_spells_effects` VALUES ('22', 'Divine Aura', '0');
INSERT INTO `npc_spells_effects` VALUES ('23', 'Cast CH on Kill', '0');
INSERT INTO `npc_spells_effects` VALUES ('24', 'Critical Heal', '0');
INSERT INTO `npc_spells_effects` VALUES ('25', 'Critical Direct Dmg', '0');
INSERT INTO `npc_spells_effects` VALUES ('26', 'Heal Rate', '0');
INSERT INTO `npc_spells_effects` VALUES ('27', 'Negate Damage Shield', '0');
INSERT INTO `npc_spells_effects` VALUES ('28', 'Increase Spell Vulnerability [All]', '0');
INSERT INTO `npc_spells_effects` VALUES ('29', 'Decrease Spell Vulnerability [FR]', '0');
INSERT INTO `npc_spells_effects` VALUES ('30', 'Movement Speed', '0');
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `npc_spells_effects_entries`
-- ----------------------------
DROP TABLE IF EXISTS `npc_spells_effects_entries`;
CREATE TABLE `npc_spells_effects_entries` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`npc_spells_effects_id` int(11) NOT NULL DEFAULT '0',
`spell_effect_id` smallint(5) NOT NULL DEFAULT '0',
`minlevel` tinyint(3) unsigned NOT NULL DEFAULT '0',
`maxlevel` tinyint(3) unsigned NOT NULL DEFAULT '255',
`se_base` int(11) NOT NULL DEFAULT '0',
`se_limit` int(11) NOT NULL DEFAULT '0',
`se_max` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `spellsid_spellid` (`npc_spells_effects_id`,`spell_effect_id`)
) ENGINE=InnoDB AUTO_INCREMENT=18374 DEFAULT CHARSET=latin1;
-- ----------------------------
-- Records of npc_spells_effects_entries
-- ----------------------------
INSERT INTO `npc_spells_effects_entries` VALUES ('1', '1', '169', '0', '255', '10000', '-1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('2', '2', '59', '0', '255', '-60', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('3', '3', '11', '0', '255', '150', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('4', '4', '180', '0', '255', '50', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('5', '5', '378', '0', '255', '85', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('6', '6', '158', '0', '255', '50', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('7', '7', '157', '0', '255', '-300', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('8', '8', '168', '0', '255', '-50', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('9', '9', '172', '0', '255', '10000', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('10', '10', '173', '0', '255', '10000', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('11', '11', '174', '0', '255', '10000', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('12', '12', '175', '0', '255', '10000', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('13', '13', '197', '0', '255', '-80', '3', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('14', '14', '197', '0', '255', '80', '1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('15', '15', '188', '0', '255', '10000', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('16', '16', '178', '0', '255', '90', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('17', '17', '184', '0', '255', '10000', '-1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('18', '18', '185', '0', '255', '100', '1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('19', '19', '301', '0', '255', '100', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('20', '20', '279', '0', '255', '50', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('21', '21', '220', '0', '255', '2000', '1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('22', '22', '40', '0', '255', '1', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('23', '23', '360', '0', '255', '100', '13', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('24', '24', '274', '0', '255', '90', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('25', '25', '294', '0', '255', '100', '200', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('26', '26', '120', '0', '255', '50', '0', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('27', '27', '382', '0', '255', '0', '55', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('28', '28', '296', '0', '255', '1000', '-1', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('29', '29', '296', '0', '255', '-50', '2', '0');
INSERT INTO `npc_spells_effects_entries` VALUES ('30', '30', '3', '0', '255', '60', '0', '0');

View File

@ -0,0 +1,3 @@
ALTER TABLE npc_types MODIFY slow_mitigation smallint(4) NOT NULL DEFAULT '0';

View File

@ -84,7 +84,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(world "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(world "z")
TARGET_LINK_LIBRARIES(world "m")
TARGET_LINK_LIBRARIES(world "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(world "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(world "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

View File

@ -56,7 +56,7 @@
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#ifndef FREEBSD
#if not defined (FREEBSD) && not defined (DARWIN)
union semun {
int val;
struct semid_ds *buf;
@ -461,7 +461,7 @@ int main(int argc, char** argv) {
if (InterserverTimer.Check()) {
InterserverTimer.Start();
database.ping();
AsyncLoadVariables(dbasync, &database);
// AsyncLoadVariables(dbasync, &database);
ReconnectCounter++;
if (ReconnectCounter >= 12) { // only create thread to reconnect every 10 minutes. previously we were creating a new thread every 10 seconds
ReconnectCounter = 0;

View File

@ -1358,7 +1358,7 @@ void Client::SendAA(uint32 id, int seq) {
if (aa_stack){
if (saa->sof_current_level > 1 && value == 0)
if (saa->sof_current_level >= 1 && value == 0)
saa->current_level = saa->sof_current_level+1;
saa->max_level = saa->sof_max_level;

View File

@ -66,7 +66,7 @@ SET(zone_sources
horse.cpp
inventory.cpp
loottables.cpp
Map.cpp
map.cpp
merc.cpp
mob.cpp
MobAI.cpp
@ -95,6 +95,7 @@ SET(zone_sources
questmgr.cpp
QuestParserCollection.cpp
raids.cpp
RaycastMesh.cpp
spawn2.cpp
spawn2.h
spawngroup.cpp
@ -183,6 +184,7 @@ SET(zone_headers
QuestParserCollection.h
raid.h
raids.h
RaycastMesh.h
skills.h
spawn2.cpp
spawn2.h
@ -230,7 +232,9 @@ IF(UNIX)
TARGET_LINK_LIBRARIES(zone "${CMAKE_DL_LIBS}")
TARGET_LINK_LIBRARIES(zone "z")
TARGET_LINK_LIBRARIES(zone "m")
TARGET_LINK_LIBRARIES(zone "rt")
IF(NOT DARWIN)
TARGET_LINK_LIBRARIES(zone "rt")
ENDIF(NOT DARWIN)
TARGET_LINK_LIBRARIES(zone "pthread")
ADD_DEFINITIONS(-fPIC)
ENDIF(UNIX)

File diff suppressed because it is too large Load Diff

View File

@ -530,6 +530,7 @@ void NPC::AI_Start(uint32 iMoveDelay) {
if (NPCTypedata) {
AI_AddNPCSpells(NPCTypedata->npc_spells_id);
ProcessSpecialAbilities(NPCTypedata->special_abilities);
AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id);
}
SendTo(GetX(), GetY(), GetZ());
@ -766,7 +767,7 @@ void Client::AI_Process()
{
bool WaypointChanged, NodeReached;
VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
Map::Vertex Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
GetFearSpeed(), WaypointChanged, NodeReached);
if(WaypointChanged)
@ -922,7 +923,7 @@ void Client::AI_Process()
else
{
bool WaypointChanged, NodeReached;
VERTEX Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(),
Map::Vertex Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(),
GetRunspeed(), WaypointChanged, NodeReached);
if(WaypointChanged)
@ -1034,7 +1035,7 @@ void Mob::AI_Process() {
{
bool WaypointChanged, NodeReached;
VERTEX Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
Map::Vertex Goal = UpdatePath(fear_walkto_x, fear_walkto_y, fear_walkto_z,
GetFearSpeed(), WaypointChanged, NodeReached);
if(WaypointChanged)
@ -1372,7 +1373,7 @@ void Mob::AI_Process() {
{
bool WaypointChanged, NodeReached;
VERTEX Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(),
Map::Vertex Goal = UpdatePath(target->GetX(), target->GetY(), target->GetZ(),
GetRunspeed(), WaypointChanged, NodeReached);
if(WaypointChanged)
@ -1627,11 +1628,32 @@ void NPC::AI_DoMovement() {
if (gridno > 0 || cur_wp==-2) {
if (movetimercompleted==true) { // time to pause at wp is over
int32 spawn_id = this->GetSpawnPointID();
LinkedListIterator<Spawn2*> iterator(zone->spawn2_list);
iterator.Reset();
Spawn2 *found_spawn = nullptr;
while(iterator.MoreElements())
{
Spawn2* cur = iterator.GetData();
iterator.Advance();
if(cur->GetID() == spawn_id)
{
found_spawn = cur;
break;
}
}
if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) {
CastToNPC()->Depop(true); //depop and resart spawn timer
if(found_spawn)
found_spawn->SetNPCPointerNull();
}
else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) {
CastToNPC()->Depop(false);//depop without spawn timer
if(found_spawn)
found_spawn->SetNPCPointerNull();
}
else {
movetimercompleted=false;
@ -1698,7 +1720,7 @@ void NPC::AI_DoMovement() {
{
bool WaypointChanged;
bool NodeReached;
VERTEX Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached);
Map::Vertex Goal = UpdatePath(cur_wp_x, cur_wp_y, cur_wp_z, walksp, WaypointChanged, NodeReached);
if(WaypointChanged)
tar_ndx = 20;
@ -1737,7 +1759,7 @@ void NPC::AI_DoMovement() {
if(!((x_pos == guard_x) && (y_pos == guard_y) && (z_pos == guard_z)))
{
bool WaypointChanged, NodeReached;
VERTEX Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached);
Map::Vertex Goal = UpdatePath(guard_x, guard_y, guard_z, walksp, WaypointChanged, NodeReached);
if(WaypointChanged)
tar_ndx = 20;
@ -2277,6 +2299,7 @@ create table npc_spells_entries (
*/
bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID);
bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max);
bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j);
bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) {
@ -2351,6 +2374,105 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) {
return true;
}
bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) {
npc_spells_effects_id = iDBSpellsEffectsID;
AIspellsEffects.clear();
if (iDBSpellsEffectsID == 0)
return false;
DBnpcspellseffects_Struct* spell_effects_list = database.GetNPCSpellsEffects(iDBSpellsEffectsID);
if (!spell_effects_list) {
return false;
}
DBnpcspellseffects_Struct* parentlist = database.GetNPCSpellsEffects(spell_effects_list->parent_list);
uint32 i;
#if MobAI_DEBUG_Spells >= 10
std::cout << "Loading NPCSpellsEffects onto " << this->GetName() << ": dbspellseffectsid=" << iDBSpellsEffectsID;
if (spell_effects_list) {
std::cout << " (found, " << spell_effects_list->numentries << "), parentlist=" << spell_effects)list->parent_list;
if (spell_effects_list->parent_list) {
if (parentlist) {
std::cout << " (found, " << parentlist->numentries << ")";
}
else
std::cout << " (not found)";
}
}
else
std::cout << " (not found)";
std::cout << std::endl;
#endif
if (parentlist) {
for (i=0; i<parentlist->numentries; i++) {
if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spelleffectid > 0) {
if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base,
parentlist->entries[i].limit, parentlist->entries[i].max))
{
AddSpellEffectToNPCList(parentlist->entries[i].spelleffectid,
parentlist->entries[i].base, parentlist->entries[i].limit,
parentlist->entries[i].max);
}
}
}
}
for (i=0; i<spell_effects_list->numentries; i++) {
if (GetLevel() >= spell_effects_list->entries[i].minlevel && GetLevel() <= spell_effects_list->entries[i].maxlevel && spell_effects_list->entries[i].spelleffectid > 0) {
AddSpellEffectToNPCList(spell_effects_list->entries[i].spelleffectid,
spell_effects_list->entries[i].base, spell_effects_list->entries[i].limit,
spell_effects_list->entries[i].max);
}
}
return true;
}
void NPC::ApplyAISpellEffects(StatBonuses* newbon)
{
if (!AI_HasSpellsEffects())
return;
for(int i=0; i < AIspellsEffects.size(); i++)
{
ApplySpellsBonuses(0, 0, newbon, 0, false, 0,-1,
true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max);
}
return;
}
// adds a spell to the list, taking into account priority and resorting list as needed.
void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max)
{
if(!iSpellEffectID)
return;
HasAISpellEffects = true;
AISpellsEffects_Struct t;
t.spelleffectid = iSpellEffectID;
t.base = base;
t.limit = limit;
t.max = max;
AIspellsEffects.push_back(t);
}
bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max) {
for (uint32 i=0; i < spelleffect_list->numentries; i++) {
if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base == base
&& spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max == max)
return true;
}
return false;
}
bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID) {
for (uint32 i=0; i < spell_list->numentries; i++) {
if (spell_list->entries[i].spellid == iSpellID)
@ -2415,6 +2537,7 @@ void NPC::AISpellsList(Client *c)
DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) {
if (iDBSpellsID == 0)
return 0;
if (!npc_spells_cache) {
npc_spells_maxid = GetMaxNPCSpellsID();
npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1];
@ -2424,11 +2547,13 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) {
npc_spells_loadtried[i] = false;
}
}
if (iDBSpellsID > npc_spells_maxid)
return 0;
if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =)
return npc_spells_cache[iDBSpellsID];
}
else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already
npc_spells_loadtried[iDBSpellsID] = true;
char errbuf[MYSQL_ERRMSG_SIZE];
@ -2439,7 +2564,7 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) {
if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
row = mysql_fetch_row(result);
uint32 tmpparent_list = atoi(row[1]);
int16 tmpattack_proc = atoi(row[2]);
uint8 tmpproc_chance = atoi(row[3]);
@ -2527,3 +2652,104 @@ uint32 ZoneDatabase::GetMaxNPCSpellsID() {
return 0;
}
DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEffectsID) {
if (iDBSpellsEffectsID == 0)
return 0;
if (!npc_spellseffects_cache) {
npc_spellseffects_maxid = GetMaxNPCSpellsEffectsID();
npc_spellseffects_cache = new DBnpcspellseffects_Struct*[npc_spellseffects_maxid+1];
npc_spellseffects_loadtried = new bool[npc_spellseffects_maxid+1];
for (uint32 i=0; i<=npc_spellseffects_maxid; i++) {
npc_spellseffects_cache[i] = 0;
npc_spellseffects_loadtried[i] = false;
}
}
if (iDBSpellsEffectsID > npc_spellseffects_maxid)
return 0;
if (npc_spellseffects_cache[iDBSpellsEffectsID]) { // it's in the cache, easy =)
return npc_spellseffects_cache[iDBSpellsEffectsID];
}
else if (!npc_spellseffects_loadtried[iDBSpellsEffectsID]) { // no reason to ask the DB again if we have failed once already
npc_spellseffects_loadtried[iDBSpellsEffectsID] = true;
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list from npc_spells_effects where id=%d", iDBSpellsEffectsID), errbuf, &result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
uint32 tmpparent_list = atoi(row[1]);
mysql_free_result(result);
if (RunQuery(query, MakeAnyLenString(&query, "SELECT spell_effect_id, minlevel, maxlevel,se_base, se_limit, se_max from npc_spells_effects_entries where npc_spells_effects_id=%d ORDER BY minlevel", iDBSpellsEffectsID), errbuf, &result)) {
safe_delete_array(query);
uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_entries_Struct) * mysql_num_rows(result));
npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize];
memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize);
npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list;
npc_spellseffects_cache[iDBSpellsEffectsID]->numentries = mysql_num_rows(result);
int j = 0;
while ((row = mysql_fetch_row(result))) {
int spell_effect_id = atoi(row[0]);
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].spelleffectid = spell_effect_id;
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].minlevel = atoi(row[1]);
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].maxlevel = atoi(row[2]);
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].base = atoi(row[3]);
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].limit = atoi(row[4]);
npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].max = atoi(row[5]);
j++;
}
mysql_free_result(result);
return npc_spellseffects_cache[iDBSpellsEffectsID];
}
else {
std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl;
safe_delete_array(query);
return 0;
}
}
else {
mysql_free_result(result);
}
}
else {
std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl;
safe_delete_array(query);
return 0;
}
return 0;
}
return 0;
}
uint32 ZoneDatabase::GetMaxNPCSpellsEffectsID() {
char errbuf[MYSQL_ERRMSG_SIZE];
char *query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells_effects"), errbuf, &result)) {
safe_delete_array(query);
if (mysql_num_rows(result) == 1) {
row = mysql_fetch_row(result);
uint32 ret = 0;
if (row[0])
ret = atoi(row[0]);
mysql_free_result(result);
return ret;
}
mysql_free_result(result);
}
else {
std::cerr << "Error in GetMaxNPCSpellsEffectsID query '" << query << "' " << errbuf << std::endl;
safe_delete_array(query);
return 0;
}
return 0;
}

941
zone/RaycastMesh.cpp Normal file
View File

@ -0,0 +1,941 @@
#include "RaycastMesh.h"
#include <math.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do
// high-speed raycasting.
//
// There are much better implementations of this available on the internet. In particular I recommend that you use
// OPCODE written by Pierre Terdiman.
// @see: http://www.codercorner.com/Opcode.htm
//
// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code.
//
// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting.
// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However,
// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders
// of magnitude; so this implementation should work fine for simple tools and utilities.
//
// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees.
// AABB = Axis Aligned Bounding Volume trees.
//
// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html
//
//
// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license.
//
// mailto:jratcliffscarab@gmail.com
//
// The official source can be found at: http://code.google.com/p/raycastmesh/
//
//
#pragma warning(disable:4100)
namespace RAYCAST_MESH
{
typedef std::vector< RmUint32 > TriVector;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* A method to compute a ray-AABB intersection.
* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990
* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500)
* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only)
*
* Hence this version is faster as well as more robust than the original one.
*
* Should work provided:
* 1) the integer representation of 0.0f is 0x00000000
* 2) the sign bit of the RmReal is the most significant one
*
* Report bugs: p.terdiman@codercorner.com
*
* \param aabb [in] the axis-aligned bounding box
* \param origin [in] ray origin
* \param dir [in] ray direction
* \param coord [out] impact coordinates
* \return true if ray intersects AABB
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define RAYAABB_EPSILON 0.00001f
//! Integer representation of a RmRealing-point value.
#define IR(x) ((RmUint32&)x)
bool intersectRayAABB(const RmReal MinB[3],const RmReal MaxB[3],const RmReal origin[3],const RmReal dir[3],RmReal coord[3])
{
bool Inside = true;
RmReal MaxT[3];
MaxT[0]=MaxT[1]=MaxT[2]=-1.0f;
// Find candidate planes.
for(RmUint32 i=0;i<3;i++)
{
if(origin[i] < MinB[i])
{
coord[i] = MinB[i];
Inside = false;
// Calculate T distances to candidate planes
if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i];
}
else if(origin[i] > MaxB[i])
{
coord[i] = MaxB[i];
Inside = false;
// Calculate T distances to candidate planes
if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i];
}
}
// Ray origin inside bounding box
if(Inside)
{
coord[0] = origin[0];
coord[1] = origin[1];
coord[2] = origin[2];
return true;
}
// Get largest of the maxT's for final choice of intersection
RmUint32 WhichPlane = 0;
if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1;
if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2;
// Check final candidate actually inside box
if(IR(MaxT[WhichPlane])&0x80000000) return false;
for(RmUint32 i=0;i<3;i++)
{
if(i!=WhichPlane)
{
coord[i] = origin[i] + MaxT[WhichPlane] * dir[i];
#ifdef RAYAABB_EPSILON
if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false;
#else
if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false;
#endif
}
}
return true; // ray hits box
}
bool intersectLineSegmentAABB(const RmReal bmin[3],const RmReal bmax[3],const RmReal p1[3],const RmReal dir[3],RmReal &dist,RmReal intersect[3])
{
bool ret = false;
if ( dist > RAYAABB_EPSILON )
{
ret = intersectRayAABB(bmin,bmax,p1,dir,intersect);
if ( ret )
{
RmReal dx = p1[0]-intersect[0];
RmReal dy = p1[1]-intersect[1];
RmReal dz = p1[2]-intersect[2];
RmReal d = dx*dx+dy*dy+dz*dz;
if ( d < dist*dist )
{
dist = sqrtf(d);
}
else
{
ret = false;
}
}
}
return ret;
}
/* a = b - c */
#define vector(a,b,c) \
(a)[0] = (b)[0] - (c)[0]; \
(a)[1] = (b)[1] - (c)[1]; \
(a)[2] = (b)[2] - (c)[2];
#define innerProduct(v,q) \
((v)[0] * (q)[0] + \
(v)[1] * (q)[1] + \
(v)[2] * (q)[2])
#define crossProduct(a,b,c) \
(a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \
(a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \
(a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1];
static inline bool rayIntersectsTriangle(const RmReal *p,const RmReal *d,const RmReal *v0,const RmReal *v1,const RmReal *v2,RmReal &t)
{
RmReal e1[3],e2[3],h[3],s[3],q[3];
RmReal a,f,u,v;
vector(e1,v1,v0);
vector(e2,v2,v0);
crossProduct(h,d,e2);
a = innerProduct(e1,h);
if (a > -0.00001 && a < 0.00001)
return(false);
f = 1/a;
vector(s,p,v0);
u = f * (innerProduct(s,h));
if (u < 0.0 || u > 1.0)
return(false);
crossProduct(q,s,e1);
v = f * innerProduct(d,q);
if (v < 0.0 || u + v > 1.0)
return(false);
// at this stage we can compute t to find out where
// the intersection point is on the line
t = f * innerProduct(e2,q);
if (t > 0) // ray intersection
return(true);
else // this means that there is a line intersection
// but not a ray intersection
return (false);
}
static RmReal computePlane(const RmReal *A,const RmReal *B,const RmReal *C,RmReal *n) // returns D
{
RmReal vx = (B[0] - C[0]);
RmReal vy = (B[1] - C[1]);
RmReal vz = (B[2] - C[2]);
RmReal wx = (A[0] - B[0]);
RmReal wy = (A[1] - B[1]);
RmReal wz = (A[2] - B[2]);
RmReal vw_x = vy * wz - vz * wy;
RmReal vw_y = vz * wx - vx * wz;
RmReal vw_z = vx * wy - vy * wx;
RmReal mag = sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z));
if ( mag < 0.000001f )
{
mag = 0;
}
else
{
mag = 1.0f/mag;
}
RmReal x = vw_x * mag;
RmReal y = vw_y * mag;
RmReal z = vw_z * mag;
RmReal D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2]));
n[0] = x;
n[1] = y;
n[2] = z;
return D;
}
#define TRI_EOF 0xFFFFFFFF
enum AxisAABB
{
AABB_XAXIS,
AABB_YAXIS,
AABB_ZAXIS
};
enum ClipCode
{
CC_MINX = (1<<0),
CC_MAXX = (1<<1),
CC_MINY = (1<<2),
CC_MAXY = (1<<3),
CC_MINZ = (1<<4),
CC_MAXZ = (1<<5),
};
class BoundsAABB
{
public:
void setMin(const RmReal *v)
{
mMin[0] = v[0];
mMin[1] = v[1];
mMin[2] = v[2];
}
void setMax(const RmReal *v)
{
mMax[0] = v[0];
mMax[1] = v[1];
mMax[2] = v[2];
}
void setMin(RmReal x,RmReal y,RmReal z)
{
mMin[0] = x;
mMin[1] = y;
mMin[2] = z;
}
void setMax(RmReal x,RmReal y,RmReal z)
{
mMax[0] = x;
mMax[1] = y;
mMax[2] = z;
}
void include(const RmReal *v)
{
if ( v[0] < mMin[0] ) mMin[0] = v[0];
if ( v[1] < mMin[1] ) mMin[1] = v[1];
if ( v[2] < mMin[2] ) mMin[2] = v[2];
if ( v[0] > mMax[0] ) mMax[0] = v[0];
if ( v[1] > mMax[1] ) mMax[1] = v[1];
if ( v[2] > mMax[2] ) mMax[2] = v[2];
}
void getCenter(RmReal *center) const
{
center[0] = (mMin[0]+mMax[0])*0.5f;
center[1] = (mMin[1]+mMax[1])*0.5f;
center[2] = (mMin[2]+mMax[2])*0.5f;
}
bool intersects(const BoundsAABB &b) const
{
if ((mMin[0] > b.mMax[0]) || (b.mMin[0] > mMax[0])) return false;
if ((mMin[1] > b.mMax[1]) || (b.mMin[1] > mMax[1])) return false;
if ((mMin[2] > b.mMax[2]) || (b.mMin[2] > mMax[2])) return false;
return true;
}
bool containsTriangle(const RmReal *p1,const RmReal *p2,const RmReal *p3) const
{
BoundsAABB b;
b.setMin(p1);
b.setMax(p1);
b.include(p2);
b.include(p3);
return intersects(b);
}
bool containsTriangleExact(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &orCode) const
{
bool ret = false;
RmUint32 andCode;
orCode = getClipCode(p1,p2,p3,andCode);
if ( andCode == 0 )
{
ret = true;
}
return ret;
}
inline RmUint32 getClipCode(const RmReal *p1,const RmReal *p2,const RmReal *p3,RmUint32 &andCode) const
{
andCode = 0xFFFFFFFF;
RmUint32 c1 = getClipCode(p1);
RmUint32 c2 = getClipCode(p2);
RmUint32 c3 = getClipCode(p3);
andCode&=c1;
andCode&=c2;
andCode&=c3;
return c1|c2|c3;
}
inline RmUint32 getClipCode(const RmReal *p) const
{
RmUint32 ret = 0;
if ( p[0] < mMin[0] )
{
ret|=CC_MINX;
}
else if ( p[0] > mMax[0] )
{
ret|=CC_MAXX;
}
if ( p[1] < mMin[1] )
{
ret|=CC_MINY;
}
else if ( p[1] > mMax[1] )
{
ret|=CC_MAXY;
}
if ( p[2] < mMin[2] )
{
ret|=CC_MINZ;
}
else if ( p[2] > mMax[2] )
{
ret|=CC_MAXZ;
}
return ret;
}
inline void clamp(const BoundsAABB &aabb)
{
if ( mMin[0] < aabb.mMin[0] ) mMin[0] = aabb.mMin[0];
if ( mMin[1] < aabb.mMin[1] ) mMin[1] = aabb.mMin[1];
if ( mMin[2] < aabb.mMin[2] ) mMin[2] = aabb.mMin[2];
if ( mMax[0] > aabb.mMax[0] ) mMax[0] = aabb.mMax[0];
if ( mMax[1] > aabb.mMax[1] ) mMax[1] = aabb.mMax[1];
if ( mMax[2] > aabb.mMax[2] ) mMax[2] = aabb.mMax[2];
}
RmReal mMin[3];
RmReal mMax[3];
};
class NodeAABB;
class NodeInterface
{
public:
virtual NodeAABB * getNode(void) = 0;
virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal) = 0;
};
class NodeAABB
{
public:
NodeAABB(void)
{
mLeft = NULL;
mRight = NULL;
mLeafTriangleIndex= TRI_EOF;
}
NodeAABB(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,RmUint32 *indices,
RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh.
RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node.
RmReal minAxisSize,
NodeInterface *callback,
TriVector &leafTriangles) // once a particular axis is less than this size, stop sub-dividing.
{
mLeft = NULL;
mRight = NULL;
mLeafTriangleIndex = TRI_EOF;
TriVector triangles;
triangles.reserve(tcount);
for (RmUint32 i=0; i<tcount; i++)
{
triangles.push_back(i);
}
mBounds.setMin( vertices );
mBounds.setMax( vertices );
const RmReal *vtx = vertices+3;
for (RmUint32 i=1; i<vcount; i++)
{
mBounds.include( vtx );
vtx+=3;
}
split(triangles,vcount,vertices,tcount,indices,0,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles);
}
NodeAABB(const BoundsAABB &aabb)
{
mBounds = aabb;
mLeft = NULL;
mRight = NULL;
mLeafTriangleIndex = TRI_EOF;
}
~NodeAABB(void)
{
}
// here is where we split the mesh..
void split(const TriVector &triangles,
RmUint32 vcount,
const RmReal *vertices,
RmUint32 tcount,
const RmUint32 *indices,
RmUint32 depth,
RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh.
RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node.
RmReal minAxisSize,
NodeInterface *callback,
TriVector &leafTriangles) // once a particular axis is less than this size, stop sub-dividing.
{
// Find the longest axis of the bounding volume of this node
RmReal dx = mBounds.mMax[0] - mBounds.mMin[0];
RmReal dy = mBounds.mMax[1] - mBounds.mMin[1];
RmReal dz = mBounds.mMax[2] - mBounds.mMin[2];
AxisAABB axis = AABB_XAXIS;
RmReal laxis = dx;
if ( dy > dx )
{
axis = AABB_YAXIS;
laxis = dy;
}
if ( dz > dx && dz > dy )
{
axis = AABB_ZAXIS;
laxis = dz;
}
RmUint32 count = triangles.size();
// if the number of triangles is less than the minimum allowed for a leaf node or...
// we have reached the maximum recursion depth or..
// the width of the longest axis is less than the minimum axis size then...
// we create the leaf node and copy the triangles into the leaf node triangle array.
if ( count < minLeafSize || depth >= maxDepth || laxis < minAxisSize )
{
// Copy the triangle indices into the leaf triangles array
mLeafTriangleIndex = leafTriangles.size(); // assign the array start location for these leaf triangles.
leafTriangles.push_back(count);
for (TriVector::const_iterator i=triangles.begin(); i!=triangles.end(); ++i)
{
RmUint32 tri = *i;
leafTriangles.push_back(tri);
}
}
else
{
RmReal center[3];
mBounds.getCenter(center);
BoundsAABB b1,b2;
splitRect(axis,mBounds,b1,b2,center);
// Compute two bounding boxes based upon the split of the longest axis
BoundsAABB leftBounds,rightBounds;
TriVector leftTriangles;
TriVector rightTriangles;
// Create two arrays; one of all triangles which intersect the 'left' half of the bounding volume node
// and another array that includes all triangles which intersect the 'right' half of the bounding volume node.
for (TriVector::const_iterator i=triangles.begin(); i!=triangles.end(); ++i)
{
RmUint32 tri = (*i);
{
RmUint32 i1 = indices[tri*3+0];
RmUint32 i2 = indices[tri*3+1];
RmUint32 i3 = indices[tri*3+2];
const RmReal *p1 = &vertices[i1*3];
const RmReal *p2 = &vertices[i2*3];
const RmReal *p3 = &vertices[i3*3];
RmUint32 addCount = 0;
RmUint32 orCode=0xFFFFFFFF;
if ( b1.containsTriangleExact(p1,p2,p3,orCode))
{
addCount++;
if ( leftTriangles.empty() )
{
leftBounds.setMin(p1);
leftBounds.setMax(p1);
}
leftBounds.include(p1);
leftBounds.include(p2);
leftBounds.include(p3);
leftTriangles.push_back(tri); // Add this triangle to the 'left triangles' array and revise the left triangles bounding volume
}
// if the orCode is zero; meaning the triangle was fully self-contiained int he left bounding box; then we don't need to test against the right
if ( orCode && b2.containsTriangleExact(p1,p2,p3,orCode))
{
addCount++;
if ( rightTriangles.empty() )
{
rightBounds.setMin(p1);
rightBounds.setMax(p1);
}
rightBounds.include(p1);
rightBounds.include(p2);
rightBounds.include(p3);
rightTriangles.push_back(tri); // Add this triangle to the 'right triangles' array and revise the right triangles bounding volume.
}
assert( addCount );
}
}
if ( !leftTriangles.empty() ) // If there are triangles in the left half then...
{
leftBounds.clamp(b1); // we have to clamp the bounding volume so it stays inside the parent volume.
mLeft = callback->getNode(); // get a new AABB node
new ( mLeft ) NodeAABB(leftBounds); // initialize it to default constructor values.
// Then recursively split this node.
mLeft->split(leftTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles);
}
if ( !rightTriangles.empty() ) // If there are triangles in the right half then..
{
rightBounds.clamp(b2); // clamps the bounding volume so it stays restricted to the size of the parent volume.
mRight = callback->getNode(); // allocate and default initialize a new node
new ( mRight ) NodeAABB(rightBounds);
// Recursively split this node.
mRight->split(rightTriangles,vcount,vertices,tcount,indices,depth+1,maxDepth,minLeafSize,minAxisSize,callback,leafTriangles);
}
}
}
void splitRect(AxisAABB axis,const BoundsAABB &source,BoundsAABB &b1,BoundsAABB &b2,const RmReal *midpoint)
{
switch ( axis )
{
case AABB_XAXIS:
{
b1.setMin( source.mMin );
b1.setMax( midpoint[0], source.mMax[1], source.mMax[2] );
b2.setMin( midpoint[0], source.mMin[1], source.mMin[2] );
b2.setMax(source.mMax);
}
break;
case AABB_YAXIS:
{
b1.setMin(source.mMin);
b1.setMax(source.mMax[0], midpoint[1], source.mMax[2]);
b2.setMin(source.mMin[0], midpoint[1], source.mMin[2]);
b2.setMax(source.mMax);
}
break;
case AABB_ZAXIS:
{
b1.setMin(source.mMin);
b1.setMax(source.mMax[0], source.mMax[1], midpoint[2]);
b2.setMin(source.mMin[0], source.mMin[1], midpoint[2]);
b2.setMax(source.mMax);
}
break;
}
}
virtual void raycast(bool &hit,
const RmReal *from,
const RmReal *to,
const RmReal *dir,
RmReal *hitLocation,
RmReal *hitNormal,
RmReal *hitDistance,
const RmReal *vertices,
const RmUint32 *indices,
RmReal &nearestDistance,
NodeInterface *callback,
RmUint32 *raycastTriangles,
RmUint32 raycastFrame,
const TriVector &leafTriangles,
RmUint32 &nearestTriIndex)
{
RmReal sect[3];
RmReal nd = nearestDistance;
if ( !intersectLineSegmentAABB(mBounds.mMin,mBounds.mMax,from,dir,nd,sect) )
{
return;
}
if ( mLeafTriangleIndex != TRI_EOF )
{
const RmUint32 *scan = &leafTriangles[mLeafTriangleIndex];
RmUint32 count = *scan++;
for (RmUint32 i=0; i<count; i++)
{
RmUint32 tri = *scan++;
if ( raycastTriangles[tri] != raycastFrame )
{
raycastTriangles[tri] = raycastFrame;
RmUint32 i1 = indices[tri*3+0];
RmUint32 i2 = indices[tri*3+1];
RmUint32 i3 = indices[tri*3+2];
const RmReal *p1 = &vertices[i1*3];
const RmReal *p2 = &vertices[i2*3];
const RmReal *p3 = &vertices[i3*3];
RmReal t;
if ( rayIntersectsTriangle(from,dir,p1,p2,p3,t))
{
bool accept = false;
if ( t == nearestDistance && tri < nearestTriIndex )
{
accept = true;
}
if ( t < nearestDistance || accept )
{
nearestDistance = t;
if ( hitLocation )
{
hitLocation[0] = from[0]+dir[0]*t;
hitLocation[1] = from[1]+dir[1]*t;
hitLocation[2] = from[2]+dir[2]*t;
}
if ( hitNormal )
{
callback->getFaceNormal(tri,hitNormal);
}
if ( hitDistance )
{
*hitDistance = t;
}
nearestTriIndex = tri;
hit = true;
}
}
}
}
}
else
{
if ( mLeft )
{
mLeft->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,vertices,indices,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
}
if ( mRight )
{
mRight->raycast(hit,from,to,dir,hitLocation,hitNormal,hitDistance,vertices,indices,nearestDistance,callback,raycastTriangles,raycastFrame,leafTriangles,nearestTriIndex);
}
}
}
NodeAABB *mLeft; // left node
NodeAABB *mRight; // right node
BoundsAABB mBounds; // bounding volume of node
RmUint32 mLeafTriangleIndex; // if it is a leaf node; then these are the triangle indices.
};
class MyRaycastMesh : public RaycastMesh, public NodeInterface
{
public:
MyRaycastMesh(RmUint32 vcount,const RmReal *vertices,RmUint32 tcount,const RmUint32 *indices,RmUint32 maxDepth,RmUint32 minLeafSize,RmReal minAxisSize)
{
mRaycastFrame = 0;
if ( maxDepth < 2 )
{
maxDepth = 2;
}
if ( maxDepth > 15 )
{
maxDepth = 15;
}
RmUint32 pow2Table[16] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536 };
mMaxNodeCount = 0;
for (RmUint32 i=0; i<=maxDepth; i++)
{
mMaxNodeCount+=pow2Table[i];
}
mNodes = new NodeAABB[mMaxNodeCount];
mNodeCount = 0;
mVcount = vcount;
mVertices = (RmReal *)::malloc(sizeof(RmReal)*3*vcount);
memcpy(mVertices,vertices,sizeof(RmReal)*3*vcount);
mTcount = tcount;
mIndices = (RmUint32 *)::malloc(sizeof(RmUint32)*tcount*3);
memcpy(mIndices,indices,sizeof(RmUint32)*tcount*3);
mRaycastTriangles = (RmUint32 *)::malloc(tcount*sizeof(RmUint32));
memset(mRaycastTriangles,0,tcount*sizeof(RmUint32));
mRoot = getNode();
mFaceNormals = NULL;
new ( mRoot ) NodeAABB(mVcount,mVertices,mTcount,mIndices,maxDepth,minLeafSize,minAxisSize,this,mLeafTriangles);
}
~MyRaycastMesh(void)
{
delete []mNodes;
::free(mVertices);
::free(mIndices);
::free(mFaceNormals);
::free(mRaycastTriangles);
}
virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance)
{
bool ret = false;
RmReal dir[3];
dir[0] = to[0] - from[0];
dir[1] = to[1] - from[1];
dir[2] = to[2] - from[2];
RmReal distance = sqrtf( dir[0]*dir[0] + dir[1]*dir[1]+dir[2]*dir[2] );
if ( distance < 0.0000000001f ) return false;
RmReal recipDistance = 1.0f / distance;
dir[0]*=recipDistance;
dir[1]*=recipDistance;
dir[2]*=recipDistance;
mRaycastFrame++;
RmUint32 nearestTriIndex=TRI_EOF;
mRoot->raycast(ret,from,to,dir,hitLocation,hitNormal,hitDistance,mVertices,mIndices,distance,this,mRaycastTriangles,mRaycastFrame,mLeafTriangles,nearestTriIndex);
return ret;
}
virtual void release(void)
{
delete this;
}
virtual const RmReal * getBoundMin(void) const // return the minimum bounding box
{
return mRoot->mBounds.mMin;
}
virtual const RmReal * getBoundMax(void) const // return the maximum bounding box.
{
return mRoot->mBounds.mMax;
}
virtual NodeAABB * getNode(void)
{
assert( mNodeCount < mMaxNodeCount );
NodeAABB *ret = &mNodes[mNodeCount];
mNodeCount++;
return ret;
}
virtual void getFaceNormal(RmUint32 tri,RmReal *faceNormal)
{
if ( mFaceNormals == NULL )
{
mFaceNormals = (RmReal *)::malloc(sizeof(RmReal)*3*mTcount);
for (RmUint32 i=0; i<mTcount; i++)
{
RmUint32 i1 = mIndices[i*3+0];
RmUint32 i2 = mIndices[i*3+1];
RmUint32 i3 = mIndices[i*3+2];
const RmReal*p1 = &mVertices[i1*3];
const RmReal*p2 = &mVertices[i2*3];
const RmReal*p3 = &mVertices[i3*3];
RmReal *dest = &mFaceNormals[i*3];
computePlane(p3,p2,p1,dest);
}
}
const RmReal *src = &mFaceNormals[tri*3];
faceNormal[0] = src[0];
faceNormal[1] = src[1];
faceNormal[2] = src[2];
}
virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance)
{
bool ret = false;
RmReal dir[3];
dir[0] = to[0] - from[0];
dir[1] = to[1] - from[1];
dir[2] = to[2] - from[2];
RmReal distance = sqrtf( dir[0]*dir[0] + dir[1]*dir[1]+dir[2]*dir[2] );
if ( distance < 0.0000000001f ) return false;
RmReal recipDistance = 1.0f / distance;
dir[0]*=recipDistance;
dir[1]*=recipDistance;
dir[2]*=recipDistance;
const RmUint32 *indices = mIndices;
const RmReal *vertices = mVertices;
RmReal nearestDistance = distance;
for (RmUint32 tri=0; tri<mTcount; tri++)
{
RmUint32 i1 = indices[tri*3+0];
RmUint32 i2 = indices[tri*3+1];
RmUint32 i3 = indices[tri*3+2];
const RmReal *p1 = &vertices[i1*3];
const RmReal *p2 = &vertices[i2*3];
const RmReal *p3 = &vertices[i3*3];
RmReal t;
if ( rayIntersectsTriangle(from,dir,p1,p2,p3,t))
{
if ( t < nearestDistance )
{
nearestDistance = t;
if ( hitLocation )
{
hitLocation[0] = from[0]+dir[0]*t;
hitLocation[1] = from[1]+dir[1]*t;
hitLocation[2] = from[2]+dir[2]*t;
}
if ( hitNormal )
{
getFaceNormal(tri,hitNormal);
}
if ( hitDistance )
{
*hitDistance = t;
}
ret = true;
}
}
}
return ret;
}
RmUint32 mRaycastFrame;
RmUint32 *mRaycastTriangles;
RmUint32 mVcount;
RmReal *mVertices;
RmReal *mFaceNormals;
RmUint32 mTcount;
RmUint32 *mIndices;
NodeAABB *mRoot;
RmUint32 mNodeCount;
RmUint32 mMaxNodeCount;
NodeAABB *mNodes;
TriVector mLeafTriangles;
};
};
using namespace RAYCAST_MESH;
RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh
const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc.
RmUint32 tcount, // The number of triangles in the source triangle mesh
const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ...
RmUint32 maxDepth, // Maximum recursion depth for the triangle mesh.
RmUint32 minLeafSize, // minimum triangles to treat as a 'leaf' node.
RmReal minAxisSize // once a particular axis is less than this size, stop sub-dividing.
)
{
MyRaycastMesh *m = new MyRaycastMesh(vcount,vertices,tcount,indices,maxDepth,minLeafSize,minAxisSize);
return static_cast< RaycastMesh * >(m);
}

60
zone/RaycastMesh.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef RAYCAST_MESH_H
#define RAYCAST_MESH_H
// This code snippet allows you to create an axis aligned bounding volume tree for a triangle mesh so that you can do
// high-speed raycasting.
//
// There are much better implementations of this available on the internet. In particular I recommend that you use
// OPCODE written by Pierre Terdiman.
// @see: http://www.codercorner.com/Opcode.htm
//
// OPCODE does a whole lot more than just raycasting, and is a rather significant amount of source code.
//
// I am providing this code snippet for the use case where you *only* want to do quick and dirty optimized raycasting.
// I have not done performance testing between this version and OPCODE; so I don't know how much slower it is. However,
// anytime you switch to using a spatial data structure for raycasting, you increase your performance by orders and orders
// of magnitude; so this implementation should work fine for simple tools and utilities.
//
// It also serves as a nice sample for people who are trying to learn the algorithm of how to implement AABB trees.
// AABB = Axis Aligned Bounding Volume trees.
//
// http://www.cgal.org/Manual/3.5/doc_html/cgal_manual/AABB_tree/Chapter_main.html
//
//
// This code snippet was written by John W. Ratcliff on August 18, 2011 and released under the MIT. license.
//
// mailto:jratcliffscarab@gmail.com
//
// The official source can be found at: http://code.google.com/p/raycastmesh/
//
//
typedef float RmReal;
typedef unsigned int RmUint32;
class RaycastMesh
{
public:
virtual bool raycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) = 0;
virtual bool bruteForceRaycast(const RmReal *from,const RmReal *to,RmReal *hitLocation,RmReal *hitNormal,RmReal *hitDistance) = 0;
virtual const RmReal * getBoundMin(void) const = 0; // return the minimum bounding box
virtual const RmReal * getBoundMax(void) const = 0; // return the maximum bounding box.
virtual void release(void) = 0;
protected:
virtual ~RaycastMesh(void) { };
};
RaycastMesh * createRaycastMesh(RmUint32 vcount, // The number of vertices in the source triangle mesh
const RmReal *vertices, // The array of vertex positions in the format x1,y1,z1..x2,y2,z2.. etc.
RmUint32 tcount, // The number of triangles in the source triangle mesh
const RmUint32 *indices, // The triangle indices in the format of i1,i2,i3 ... i4,i5,i6, ...
RmUint32 maxDepth=15, // Maximum recursion depth for the triangle mesh.
RmUint32 minLeafSize=4, // minimum triangles to treat as a 'leaf' node.
RmReal minAxisSize=0.01f // once a particular axis is less than this size, stop sub-dividing.
);
#endif

View File

@ -80,6 +80,7 @@
#define CANNOT_AFFECT_NPC 251 //That spell can not affect this target NPC.
#define SUSPEND_MINION_HAS_AGGRO 256 //Your pet is the focus of something's attention.
#define NO_PET 255 //You do not have a pet.
#define GATE_FAIL 260 //Your gate is too unstable, and collapses.
#define CORPSE_CANT_SENSE 262 //You cannot sense any corpses for this PC in this zone.
#define SPELL_NO_HOLD 263 //Your spell did not take hold.
#define CANNOT_CHARM 267 //This NPC cannot be charmed.
@ -208,6 +209,7 @@
#define AA_POINTS 1215 //points
#define SPELL_FIZZLE_OTHER 1218 //%1's spell fizzles!
#define MISSED_NOTE_OTHER 1219 //A missed note brings %1's song to a close!
#define SPELL_LEVEL_REQ 1226 //This spell only works on people who are level %1 and under.
#define CORPSE_DECAY_NOW 1227 //This corpse is waiting to expire.
#define SURNAME_REJECTED 1374 //Your new surname was rejected. Please try a different name.
#define DUEL_DECLINE 1383 //%1 has declined your challenge to duel to the death.
@ -241,24 +243,26 @@
#define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1!
#define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end.
#define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again.
#define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can!
#define CORPSEDRAG_ALREADY 4062 //You are already dragging %1.
#define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can!
#define CORPSEDRAG_ALREADY 4062 //You are already dragging %1.
#define CORPSEDRAG_SOMEONE_ELSE 4063 //Someone else is dragging %1.
#define CORPSEDRAG_BEGIN 4064 //You begin to drag %1.
#define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses.
#define CORPSEDRAG_STOP 4066 //You stop dragging the corpse.
#define CORPSEDRAG_BEGIN 4064 //You begin to drag %1.
#define CORPSEDRAG_STOPALL 4065 //You stop dragging the corpses.
#define CORPSEDRAG_STOP 4066 //You stop dragging the corpse.
#define WHOALL_NO_RESULTS 5029 //There are no players in EverQuest that match those who filters.
#define PETITION_NO_DELETE 5053 //You do not have a petition in the queue.
#define PETITION_DELETED 5054 //Your petition was successfully deleted.
#define GAIN_RAIDEXP 5085 //You gained raid experience!
#define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there.
#define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure.
#define SUCCOR_FAIL 5169 //The portal collapes before you can escape!
#define PET_ATTACKING 5501 //%1 tells you, 'Attacking %2 Master.'
#define FATAL_BOW_SHOT 5745 //%1 performs a FATAL BOW SHOT!!
#define MELEE_SILENCE 5806 //You *CANNOT* use this melee ability, you are suffering from amnesia!
#define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds.
#define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds.
#define FAILED_TAUNT 5811 //You have failed to taunt your target.
#define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability.
#define AA_NO_TARGET 5825 //You must first select a target for this ability!
#define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else!
#define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited.
@ -296,6 +300,11 @@
#define GAIN_RAID_LEADERSHIP_EXP 8789 //
#define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining)
#define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell.
#define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful.
#define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful.
#define SLOW_SLIGHTLY_SUCCESSFUL 9031 //Your spell was slightly successful.
#define SPELL_OPPOSITE_EFFECT 9032 //Your spell may have had the opposite effect of what you desired.
#define HAS_BEEN_AWAKENED 9037 //%1 has been awakened by %2.
#define YOU_HEAL 9068 //You have healed %1 for %2 points of damage.
#define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3.
#define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage.
@ -330,6 +339,7 @@
#define ALREADY_CASTING 12442 //You are already casting a spell!
#define SENSE_CORPSE_NOT_NAME 12446 //You don't sense any corpses of that name.
#define SENSE_CORPSE_NONE 12447 //You don't sense any corpses.
#define SCREECH_BUFF_BLOCK 12448 //Your immunity buff protected you from the spell %1!
#define NOT_HOLDING_ITEM 12452 //You are not holding an item!
#define SENSE_UNDEAD 12471 //You sense undead in this direction.
#define SENSE_ANIMAL 12472 //You sense an animal in this direction.

View File

@ -880,133 +880,6 @@ bool Mob::CombatRange(Mob* other)
return false;
}
//Old LOS function, prolly not used anymore
//Not removed because I havent looked it over to see if anything
//useful is in here before we delete it.
bool Mob::CheckLos(Mob* other) {
if (zone->zonemap == 0)
{
return true;
}
float tmp_x = GetX();
float tmp_y = GetY();
float tmp_z = GetZ();
float trg_x = other->GetX();
float trg_y = other->GetY();
float trg_z = other->GetZ();
float perwalk_x = 0.5;
float perwalk_y = 0.5;
float perwalk_z = 0.5;
float dist_x = tmp_x - trg_x;
if (dist_x < 0)
dist_x *= -1;
float dist_y = tmp_y - trg_y;
if (dist_y < 0)
dist_y *= -1;
float dist_z = tmp_z - trg_z;
if (dist_z < 0)
dist_z *= -1;
if (dist_x < dist_y && dist_z < dist_y)
{
perwalk_x /= (dist_y/dist_x);
perwalk_z /= (dist_y/dist_z);
}
else if (dist_y < dist_x && dist_z < dist_x)
{
perwalk_y /= (dist_x/dist_y);
perwalk_z /= (dist_x/dist_z);
}
else if (dist_x < dist_z && dist_y < dist_z)
{
perwalk_x /= (dist_z/dist_x);
perwalk_y /= (dist_z/dist_y);
}
float steps = (dist_x/perwalk_x + dist_y/perwalk_y + dist_z/perwalk_z)*10; //Just a safety check to prevent endless loops.
while (steps > 0) {
steps--;
if (tmp_x < trg_x)
{
if (tmp_x + perwalk_x < trg_x)
tmp_x += perwalk_x;
else
tmp_x = trg_x;
}
if (tmp_y < trg_y)
{
if (tmp_y + perwalk_y < trg_y)
tmp_y += perwalk_y;
else
tmp_y = trg_y;
}
if (tmp_z < trg_z)
{
if (tmp_z + perwalk_z < trg_z)
tmp_z += perwalk_z;
else
tmp_z = trg_z;
}
if (tmp_x > trg_x)
{
if (tmp_x - perwalk_x > trg_x)
tmp_x -= perwalk_x;
else
tmp_x = trg_x;
}
if (tmp_y > trg_y)
{
if (tmp_y - perwalk_y > trg_y)
tmp_y -= perwalk_y;
else
tmp_y = trg_y;
}
if (tmp_z > trg_z)
{
if (tmp_z - perwalk_z > trg_z)
tmp_z -= perwalk_z;
else
tmp_z = trg_z;
}
if (tmp_y == trg_y && tmp_x == trg_x && tmp_z == trg_z)
{
return true;
}
//I believe this is contributing to breaking mob spawns when a map is loaded
// NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), tmp_x, tmp_y );
NodeRef pnode = NODE_NONE;
if (pnode != NODE_NONE)
{
const int *iface = zone->zonemap->SeekFace( pnode, tmp_x, tmp_y );
if (*iface == -1) {
return false;
}
float temp_z = 0;
float best_z = 999999;
while(*iface != -1)
{
temp_z = zone->zonemap->GetFaceHeight( *iface, x_pos, y_pos );
//UMM.. OMG... sqrtf(pow(x, 2)) == x.... retards
float best_dist = sqrtf((float)(pow(best_z-tmp_z, 2)));
float tmp_dist = sqrtf((float)(pow(tmp_z-tmp_z, 2)));
if (tmp_dist < best_dist)
{
best_z = temp_z;
}
iface++;
}
/* solar: our aggro code isn't using this right now, just spells, so i'm
taking out the +-10 check for now to make it work right on hills
if (best_z - 10 > trg_z || best_z + 10 < trg_z)
{
return false;
}
*/
}
}
return true;
}
//Father Nitwit's LOS code
bool Mob::CheckLosFN(Mob* other) {
bool Result = false;
@ -1028,8 +901,8 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) {
#endif
}
VERTEX myloc;
VERTEX oloc;
Map::Vertex myloc;
Map::Vertex oloc;
#define LOS_DEFAULT_HEIGHT 6.0f
@ -1044,72 +917,7 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) {
#if LOSDEBUG>=5
LogFile->write(EQEMuLog::Debug, "LOS from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f) sizes: (%.2f, %.2f)", myloc.x, myloc.y, myloc.z, oloc.x, oloc.y, oloc.z, GetSize(), mobSize);
#endif
FACE *onhit;
NodeRef mynode;
NodeRef onode;
VERTEX hit;
//see if anything in our node is in the way
mynode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), myloc.x, myloc.y);
if(mynode != NODE_NONE) {
if(zone->zonemap->LineIntersectsNode(mynode, myloc, oloc, &hit, &onhit)) {
#if LOSDEBUG>=5
LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, cannot see.", GetName());
LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n",
onhit->a.x, onhit->a.y, onhit->a.z,
onhit->b.x, onhit->b.y, onhit->b.z,
onhit->c.x, onhit->c.y, onhit->c.z);
#endif
return(false);
}
}
#if LOSDEBUG>=5
else {
LogFile->write(EQEMuLog::Debug, "WTF, I have no node, what am I standing on??? (%.2f, %.2f).", myloc.x, myloc.y);
}
#endif
//see if they are in a different node.
//if so, see if anything in their node is blocking me.
if(! zone->zonemap->LocWithinNode(mynode, oloc.x, oloc.y)) {
onode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), oloc.x, oloc.y);
if(onode != NODE_NONE && onode != mynode) {
if(zone->zonemap->LineIntersectsNode(onode, myloc, oloc, &hit, &onhit)) {
#if LOSDEBUG>=5
LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, cannot see (2).", GetName());
LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n",
onhit->a.x, onhit->a.y, onhit->a.z,
onhit->b.x, onhit->b.y, onhit->b.z,
onhit->c.x, onhit->c.y, onhit->c.z);
#endif
return(false);
}
}
#if LOSDEBUG>=5
else if(onode == NODE_NONE) {
LogFile->write(EQEMuLog::Debug, "WTF, They have no node, what are they standing on??? (%.2f, %.2f).", myloc.x, myloc.y);
}
#endif
}
/*
if(zone->zonemap->LineIntersectsZone(myloc, oloc, CHECK_LOS_STEP, &onhit)) {
#if LOSDEBUG>=5
LogFile->write(EQEMuLog::Debug, "Check LOS for %s target %s, cannot see.", GetName(), other->GetName() );
LogFile->write(EQEMuLog::Debug, "\tPoly: (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)\n",
onhit->a.x, onhit->a.y, onhit->a.z,
onhit->b.x, onhit->b.y, onhit->b.z,
onhit->c.x, onhit->c.y, onhit->c.z);
#endif
return(false);
}*/
#if LOSDEBUG>=5
LogFile->write(EQEMuLog::Debug, "Check LOS for %s target position, CAN SEE.", GetName());
#endif
return(true);
return zone->zonemap->CheckLoS(myloc, oloc);
}
//offensive spell aggro

View File

@ -1371,6 +1371,9 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
@ -1983,6 +1986,9 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden)
{
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
@ -2114,7 +2120,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack
if(p_depop == true)
return false;
HasAISpellEffects = false;
BuffFadeAll();
uint8 killed_level = GetLevel();
@ -3610,6 +3617,8 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons
//fade mez if we are mezzed
if (IsMezzed()) {
mlog(COMBAT__HITS, "Breaking mez due to attack.");
entity_list.MessageClose_StringID(this, true, 100, MT_WornOff,
HAS_BEEN_AWAKENED, GetCleanName(), attacker->GetCleanName());
BuffFadeByEffect(SE_Mez);
}
@ -4079,6 +4088,10 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on
}
}
}
//If OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not.
//This is for some servers that may want to have as many procs triggering from weapons as possible in a single round.
if(!RuleB(Combat, OneProcPerWeapon))
proced = false;
if (!proced && inst) {
for (int r = 0; r < MAX_AUGMENT_SLOTS; r++) {
@ -4103,7 +4116,8 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on
}
} else {
ExecWeaponProc(aug_i, aug->Proc.Effect, on);
break;
if (RuleB(Combat, OneProcPerWeapon))
break;
}
}
}

View File

@ -1158,14 +1158,20 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon)
break;
}
case SE_SpellEffectResistChance:
{
for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2)
{
if(!newbon->SEResist[e] || ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < base1)) ){
newbon->SEResist[e] = base2;
newbon->SEResist[e+1] = base1;
break;
if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < base1)){
newbon->SEResist[e] = base2; //Spell Effect ID
newbon->SEResist[e+1] = base1; //Resist Chance
break;
}
else if (!newbon->SEResist[e+1]){
newbon->SEResist[e] = base2; //Spell Effect ID
newbon->SEResist[e+1] = base1; //Resist Chance
break;
}
}
break;
@ -1258,6 +1264,10 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon)
}
}
//Applies any perma NPC spell bonuses from npc_spells_effects table.
if (IsNPC())
CastToNPC()->ApplyAISpellEffects(newbon);
//Removes the spell bonuses that are effected by a 'negate' debuff.
if (spellbonuses.NegateEffects){
for(i = 0; i < buff_count; i++) {
@ -1270,12 +1280,13 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon)
if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells.
}
void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot)
void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot,
bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max)
{
int i, effect_value;
int i, effect_value, base2, max, effectid;
Mob *caster = nullptr;
if(!IsValidSpell(spell_id))
if(!IsAISpellEffect && !IsValidSpell(spell_id))
return;
if(casterId > 0)
@ -1283,19 +1294,35 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
for (i = 0; i < EFFECT_COUNT; i++)
{
if(IsBlankSpellEffect(spell_id, i))
continue;
//Buffs/Item effects
if (!IsAISpellEffect) {
uint8 focus = IsFocusEffect(spell_id, i);
if (focus)
{
newbon->FocusEffects[focus] = spells[spell_id].effectid[i];
continue;
if(IsBlankSpellEffect(spell_id, i))
continue;
uint8 focus = IsFocusEffect(spell_id, i);
if (focus)
{
newbon->FocusEffects[focus] = spells[spell_id].effectid[i];
continue;
}
effectid = spells[spell_id].effectid[i];
effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining);
base2 = spells[spell_id].base2[i];
max = spells[spell_id].max[i];
}
//Use AISpellEffects
else {
effectid = effect_id;
effect_value = se_base;
base2 = se_limit;
max = se_max;
i = EFFECT_COUNT; //End the loop
}
effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining);
switch (spells[spell_id].effectid[i])
switch (effectid)
{
case SE_CurrentHP: //regens
if(effect_value > 0) {
@ -1338,19 +1365,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
}
}
else if ((effect_value - 100) < 0) { // Slow
//Slow Mitigation works by taking the amount that would be slowed, and adding a multiplied version of the difference.
int real_slow_value = (100 - effect_value) * -1;
if (slow_mitigation){
int new_effect_value = SlowMitigation(false,caster,real_slow_value);
if (new_effect_value < newbon->haste) {
newbon->haste = new_effect_value;
SlowMitigation(true,caster);
}
}
else {
if (real_slow_value < newbon->haste)
newbon->haste = real_slow_value;
}
real_slow_value -= ((real_slow_value * GetSlowMitigation()/100));
if (real_slow_value < newbon->haste)
newbon->haste = real_slow_value;
}
break;
}
@ -1358,16 +1376,29 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_AttackSpeed2:
{
if ((effect_value - 100) > 0) { // Haste V2 - Stacks with V1 but does not Overcap
if (newbon->hastetype2 < 0) break; //Slowed - Don't apply haste2
if ((effect_value - 100) > newbon->hastetype2) {
newbon->hastetype2 = effect_value - 100;
}
}
else if ((effect_value - 100) < 0) { // Slow
int real_slow_value = (100 - effect_value) * -1;
real_slow_value -= ((real_slow_value * GetSlowMitigation()/100));
if (real_slow_value < newbon->hastetype2)
newbon->hastetype2 = real_slow_value;
}
break;
}
case SE_AttackSpeed3:
{
if (effect_value > 0) { // Haste V3 - Stacks and Overcaps
if (effect_value < 0){ //Slow
effect_value -= ((effect_value * GetSlowMitigation()/100));
if (effect_value < newbon->hastetype3)
newbon->hastetype3 = effect_value;
}
else if (effect_value > 0) { // Haste V3 - Stacks and Overcaps
if (effect_value > newbon->hastetype3) {
newbon->hastetype3 = effect_value;
}
@ -1377,18 +1408,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_AttackSpeed4:
{
if (effect_value > 0) {
if (slow_mitigation){
int new_effect_value = SlowMitigation(false,caster,effect_value);
if (new_effect_value > newbon->inhibitmelee) {
newbon->inhibitmelee = new_effect_value;
SlowMitigation(true,caster);
}
}
else if (effect_value > newbon->inhibitmelee) {
newbon->inhibitmelee = effect_value;
}
if (effect_value < 0) //A few spells use negative values(Descriptions all indicate it should be a slow)
effect_value = effect_value * -1;
if (effect_value > 0 && effect_value > newbon->inhibitmelee) {
effect_value -= ((effect_value * GetSlowMitigation()/100));
if (effect_value > newbon->inhibitmelee)
newbon->inhibitmelee = effect_value;
}
break;
}
@ -1630,27 +1658,27 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_CriticalHitChance:
{
if (RuleB(Spells, AdditiveBonusValues) && item_bonus) {
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->CriticalHitChance[HIGHEST_SKILL+1] += effect_value;
else
newbon->CriticalHitChance[spells[spell_id].base2[i]] += effect_value;
newbon->CriticalHitChance[base2] += effect_value;
}
else if(effect_value < 0) {
if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value)
if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value)
newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value;
else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] > effect_value)
newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value;
else if(base2 != -1 && newbon->CriticalHitChance[base2] > effect_value)
newbon->CriticalHitChance[base2] = effect_value;
}
else if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value)
else if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value)
newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value;
else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] < effect_value)
newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value;
else if(base2 != -1 && newbon->CriticalHitChance[base2] < effect_value)
newbon->CriticalHitChance[base2] = effect_value;
break;
}
@ -1769,10 +1797,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
newbon->MeleeLifetap += spells[spell_id].base[i];
else if((effect_value < 0) && (newbon->MeleeLifetap > effect_value))
newbon->MeleeLifetap = spells[spell_id].base[i];
newbon->MeleeLifetap = effect_value;
else if(newbon->MeleeLifetap < spells[spell_id].base[i])
newbon->MeleeLifetap = spells[spell_id].base[i];
else if(newbon->MeleeLifetap < effect_value)
newbon->MeleeLifetap = effect_value;
break;
}
@ -1811,7 +1839,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_HundredHands:
{
if (RuleB(Spells, AdditiveBonusValues) && item_bonus)
newbon->HundredHands += spells[spell_id].base[i];
newbon->HundredHands += effect_value;
if (effect_value > 0 && effect_value > newbon->HundredHands)
newbon->HundredHands = effect_value; //Increase Weapon Delay
@ -1824,7 +1852,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
{
if(newbon->MeleeSkillCheck < effect_value) {
newbon->MeleeSkillCheck = effect_value;
newbon->MeleeSkillCheckSkill = spells[spell_id].base2[i]==-1?255:spells[spell_id].base2[i];
newbon->MeleeSkillCheckSkill = base2==-1?255:base2;
}
break;
}
@ -1833,13 +1861,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
{
if (RuleB(Spells, AdditiveBonusValues) && item_bonus){
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->HitChanceEffect[HIGHEST_SKILL+1] += effect_value;
else
newbon->HitChanceEffect[spells[spell_id].base2[i]] += effect_value;
newbon->HitChanceEffect[base2] += effect_value;
}
else if(spells[spell_id].base2[i] == -1){
else if(base2 == -1){
if ((effect_value < 0) && (newbon->HitChanceEffect[HIGHEST_SKILL+1] > effect_value))
newbon->HitChanceEffect[HIGHEST_SKILL+1] = effect_value;
@ -1851,12 +1879,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
else {
if ((effect_value < 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] > effect_value))
newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value;
if ((effect_value < 0) && (newbon->HitChanceEffect[base2] > effect_value))
newbon->HitChanceEffect[base2] = effect_value;
else if (!newbon->HitChanceEffect[spells[spell_id].base2[i]] ||
((newbon->HitChanceEffect[spells[spell_id].base2[i]] > 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] < effect_value)))
newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value;
else if (!newbon->HitChanceEffect[base2] ||
((newbon->HitChanceEffect[base2] > 0) && (newbon->HitChanceEffect[base2] < effect_value)))
newbon->HitChanceEffect[base2] = effect_value;
}
break;
@ -1865,19 +1893,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_DamageModifier:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->DamageModifier[HIGHEST_SKILL+1] += effect_value;
else
newbon->DamageModifier[spells[spell_id].base2[i]] += effect_value;
newbon->DamageModifier[base2] += effect_value;
break;
}
case SE_MinDamageModifier:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->MinDamageModifier[HIGHEST_SKILL+1] += effect_value;
else
newbon->MinDamageModifier[spells[spell_id].base2[i]] += effect_value;
newbon->MinDamageModifier[base2] += effect_value;
break;
}
@ -1920,8 +1948,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
newbon->DeathSave[0] = effect_value; //1='Partial' 2='Full'
newbon->DeathSave[1] = buffslot;
//These are used in later expansion spell effects.
newbon->DeathSave[2] = spells[spell_id].base2[i];//Min level for HealAmt
newbon->DeathSave[3] = spells[spell_id].max[i];//HealAmt
newbon->DeathSave[2] = base2;//Min level for HealAmt
newbon->DeathSave[3] = max;//HealAmt
}
break;
}
@ -1936,7 +1964,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
else if(newbon->DivineSaveChance[0] < effect_value)
{
newbon->DivineSaveChance[0] = effect_value;
newbon->DivineSaveChance[1] = spells[spell_id].base2[i];
newbon->DivineSaveChance[1] = base2;
//SetDeathSaveChance(true);
}
break;
@ -1971,10 +1999,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_SkillDamageTaken:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->SkillDmgTaken[HIGHEST_SKILL+1] += effect_value;
else
newbon->SkillDmgTaken[spells[spell_id].base2[i]] += effect_value;
newbon->SkillDmgTaken[base2] += effect_value;
break;
}
@ -1999,8 +2027,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
{
newbon->CriticalSpellChance += effect_value;
if (spells[spell_id].base2[i] > newbon->SpellCritDmgIncNoStack)
newbon->SpellCritDmgIncNoStack = spells[spell_id].base2[i];
if (base2 > newbon->SpellCritDmgIncNoStack)
newbon->SpellCritDmgIncNoStack = base2;
break;
}
@ -2052,9 +2080,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
if(!newbon->SpellOnKill[e])
{
// Base2 = Spell to fire | Base1 = % chance | Base3 = min level
newbon->SpellOnKill[e] = spells[spell_id].base2[i];
newbon->SpellOnKill[e] = base2;
newbon->SpellOnKill[e+1] = effect_value;
newbon->SpellOnKill[e+2] = spells[spell_id].max[i];
newbon->SpellOnKill[e+2] = max;
break;
}
}
@ -2068,7 +2096,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
if(!newbon->SpellOnDeath[e])
{
// Base2 = Spell to fire | Base1 = % chance
newbon->SpellOnDeath[e] = spells[spell_id].base2[i];
newbon->SpellOnDeath[e] = base2;
newbon->SpellOnDeath[e+1] = effect_value;
break;
}
@ -2078,26 +2106,26 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_CriticalDamageMob:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->CritDmgMob[HIGHEST_SKILL+1] += effect_value;
else
newbon->CritDmgMob[spells[spell_id].base2[i]] += effect_value;
newbon->CritDmgMob[base2] += effect_value;
break;
}
case SE_ReduceSkillTimer:
{
if(newbon->SkillReuseTime[spells[spell_id].base2[i]] < effect_value)
newbon->SkillReuseTime[spells[spell_id].base2[i]] = effect_value;
if(newbon->SkillReuseTime[base2] < effect_value)
newbon->SkillReuseTime[base2] = effect_value;
break;
}
case SE_SkillDamageAmount:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->SkillDamageAmount[HIGHEST_SKILL+1] += effect_value;
else
newbon->SkillDamageAmount[spells[spell_id].base2[i]] += effect_value;
newbon->SkillDamageAmount[base2] += effect_value;
break;
}
@ -2187,10 +2215,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_SkillDamageAmount2:
{
if(spells[spell_id].base2[i] == -1)
if(base2 == -1)
newbon->SkillDamageAmount2[HIGHEST_SKILL+1] += effect_value;
else
newbon->SkillDamageAmount2[spells[spell_id].base2[i]] += effect_value;
newbon->SkillDamageAmount2[base2] += effect_value;
break;
}
@ -2218,7 +2246,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
if (newbon->MeleeThresholdGuard[0] < effect_value){
newbon->MeleeThresholdGuard[0] = effect_value;
newbon->MeleeThresholdGuard[1] = buffslot;
newbon->MeleeThresholdGuard[2] = spells[spell_id].base2[i];
newbon->MeleeThresholdGuard[2] = base2;
}
break;
}
@ -2228,7 +2256,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
if (newbon->SpellThresholdGuard[0] < effect_value){
newbon->SpellThresholdGuard[0] = effect_value;
newbon->SpellThresholdGuard[1] = buffslot;
newbon->SpellThresholdGuard[2] = spells[spell_id].base2[i];
newbon->SpellThresholdGuard[2] = base2;
}
break;
}
@ -2262,20 +2290,20 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_TriggerMeleeThreshold:
{
if (newbon->TriggerMeleeThreshold[2] < spells[spell_id].base2[i]){
if (newbon->TriggerMeleeThreshold[2] < base2){
newbon->TriggerMeleeThreshold[0] = effect_value;
newbon->TriggerMeleeThreshold[1] = buffslot;
newbon->TriggerMeleeThreshold[2] = spells[spell_id].base2[i];
newbon->TriggerMeleeThreshold[2] = base2;
}
break;
}
case SE_TriggerSpellThreshold:
{
if (newbon->TriggerSpellThreshold[2] < spells[spell_id].base2[i]){
if (newbon->TriggerSpellThreshold[2] < base2){
newbon->TriggerSpellThreshold[0] = effect_value;
newbon->TriggerSpellThreshold[1] = buffslot;
newbon->TriggerSpellThreshold[2] = spells[spell_id].base2[i];
newbon->TriggerSpellThreshold[2] = base2;
}
break;
}
@ -2290,7 +2318,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_ShieldEquipDmgMod:
newbon->ShieldEquipDmgMod[0] += effect_value;
newbon->ShieldEquipDmgMod[1] += spells[spell_id].base2[i];
newbon->ShieldEquipDmgMod[1] += base2;
break;
case SE_BlockBehind:
@ -2379,7 +2407,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
break;
case SE_AddSingingMod:
switch (spells[spell_id].base2[i])
switch (base2)
{
case ItemTypeWindInstrument:
newbon->windMod += effect_value;
@ -2472,10 +2500,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
{
for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2)
{
if(!newbon->SEResist[e] &&
((newbon->SEResist[e] = spells[spell_id].base2[i]) && (newbon->SEResist[e+1] < effect_value)) ){
newbon->SEResist[e] = spells[spell_id].base2[i];
newbon->SEResist[e+1] = effect_value;
if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < effect_value)){
newbon->SEResist[e] = base2; //Spell Effect ID
newbon->SEResist[e+1] = effect_value; //Resist Chance
break;
}
else if (!newbon->SEResist[e+1]){
newbon->SEResist[e] = base2; //Spell Effect ID
newbon->SEResist[e+1] = effect_value; //Resist Chance
break;
}
}
@ -2492,7 +2524,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_GiveDoubleRiposte:
{
//Only allow for regular double riposte chance.
if(newbon->GiveDoubleRiposte[spells[spell_id].base2[i]] == 0){
if(newbon->GiveDoubleRiposte[base2] == 0){
if(newbon->GiveDoubleRiposte[0] < effect_value)
newbon->GiveDoubleRiposte[0] = effect_value;
}
@ -2503,7 +2535,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
{
if(newbon->SlayUndead[1] < effect_value)
newbon->SlayUndead[0] = effect_value; // Rate
newbon->SlayUndead[1] = spells[spell_id].base2[i]; // Damage Modifier
newbon->SlayUndead[1] = base2; // Damage Modifier
break;
}
@ -2519,7 +2551,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
case SE_ImprovedTaunt:
if (newbon->ImprovedTaunt[0] < effect_value) {
newbon->ImprovedTaunt[0] = effect_value;
newbon->ImprovedTaunt[1] = spells[spell_id].base2[i];
newbon->ImprovedTaunt[1] = base2;
newbon->ImprovedTaunt[2] = buffslot;
}
break;
@ -2530,7 +2562,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
break;
case SE_FrenziedDevastation:
newbon->FrenziedDevastation += spells[spell_id].base2[i];
newbon->FrenziedDevastation += base2;
break;
case SE_Root:
@ -2568,6 +2600,23 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
newbon->AbsorbMagicAtt[1] = buffslot;
}
break;
case SE_NegateIfCombat:
newbon->NegateIfCombat = true;
break;
case SE_Screech:
newbon->Screech = effect_value;
break;
//Special custom cases for loading effects on to NPC from 'npc_spels_effects' table
if (IsAISpellEffect) {
//Non-Focused Effect to modify incomming spell damage by resist type.
case SE_FcSpellVulnerability:
ModVulnerability(base2, effect_value);
break;
}
}
}
}

View File

@ -3237,6 +3237,9 @@ void Bot::BotRangedAttack(Mob* other) {
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
@ -5281,6 +5284,28 @@ uint32 Bot::GetBotOwnerCharacterID(uint32 botID, std::string* errorMessage) {
return Result;
}
void Bot::LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp) {
// This essentially performs a '#bot update,' with appearance packets, based on the current methods.
// This should not be called outside of Client::SetEXP() due to it's lack of rule checks.
if(client) {
std::list<Bot*> blist = entity_list.GetBotsByBotOwnerCharacterID(client->CharacterID());
for(std::list<Bot*>::iterator biter = blist.begin(); biter != blist.end(); ++biter) {
Bot* bot = *biter;
if(bot && (bot->GetLevel() != client->GetLevel())) {
bot->SetPetChooser(false); // not sure what this does, but was in bot 'update' code
bot->CalcBotStats(false);
if(sendlvlapp)
bot->SendLevelAppearance();
// modified from Client::SetLevel()
bot->SendAppearancePacket(AT_WhoLevel, level, true, true); // who level change
}
}
blist.clear();
}
}
std::string Bot::ClassIdToString(uint16 classId) {
std::string Result;
@ -6640,6 +6665,9 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
safe_delete(outapp);
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(GetTarget())
TriggerDefensiveProcs(weapon, other, Hand, damage);

View File

@ -352,6 +352,7 @@ public:
static uint32 CreatedBotCount(uint32 botOwnerCharacterID, std::string* errorMessage);
static uint32 AllowedBotSpawns(uint32 botOwnerCharacterID, std::string* errorMessage);
static uint32 GetBotOwnerCharacterID(uint32 botID, std::string* errorMessage);
static void LevelBotWithClient(Client* client, uint8 level, bool sendlvlapp);
//static bool SetBotOwnerCharacterID(uint32 botID, uint32 botOwnerCharacterID, std::string* errorMessage);
static std::string ClassIdToString(uint16 classId);
static std::string RaceIdToString(uint16 raceId);

View File

@ -1022,11 +1022,12 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s
if(command_dispatch(this, message) == -2) {
if(parse->PlayerHasQuestSub(EVENT_COMMAND)) {
int i = parse->EventPlayer(EVENT_COMMAND, this, message, 0);
if(i == 0) {
if(i == 0 && !RuleB(Chat, SuppressCommandErrors)) {
Message(13, "Command '%s' not recognized.", message);
}
} else {
Message(13, "Command '%s' not recognized.", message);
if(!RuleB(Chat, SuppressCommandErrors))
Message(13, "Command '%s' not recognized.", message);
}
}
break;
@ -2306,6 +2307,8 @@ uint64 Client::GetAllMoney() {
}
bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int chancemodi) {
if (IsDead() || IsUnconscious())
return false;
if (IsAIControlled()) // no skillups while chamred =p
return false;
if (skillid > HIGHEST_SKILL)
@ -2349,6 +2352,10 @@ bool Client::CheckIncreaseSkill(SkillUseTypes skillid, Mob *against_who, int cha
}
void Client::CheckLanguageSkillIncrease(uint8 langid, uint8 TeacherSkill) {
if (IsDead() || IsUnconscious())
return;
if (IsAIControlled())
return;
if (langid >= MAX_PP_LANGUAGE)
return; // do nothing if langid is an invalid language
@ -8155,7 +8162,7 @@ void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 1
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "Eating from slot:%i", (int)slot);
#endif
}
@ -8172,7 +8179,7 @@ void Client::Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
#if EQDEBUG >= 1
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "Drinking from slot:%i", (int)slot);
#endif
}
@ -8207,3 +8214,30 @@ void Client::PlayMP3(const char* fname)
QueuePacket(outapp);
safe_delete(outapp);
}
void Client::ExpeditionSay(const char *str, int ExpID) {
char errbuf[MYSQL_ERRMSG_SIZE];
char* query = 0;
MYSQL_RES *result;
MYSQL_ROW row;
if (!database.RunQuery(query,MakeAnyLenString(&query, "SELECT `player_name` FROM `cust_inst_players` WHERE `inst_id` = %i", ExpID),errbuf,&result)){
safe_delete_array(query);
return;
}
safe_delete_array(query);
if(result)
this->Message(14, "You say to the expedition, '%s'", str);
while((row = mysql_fetch_row(result))) {
const char* CharName = row[0];
if(strcmp(CharName, this->GetCleanName()) != 0)
worldserver.SendEmoteMessage(CharName, 0, 0, 14, "%s says to the expedition, '%s'", this->GetCleanName(), str);
// ChannelList->CreateChannel(ChannelName, ChannelOwner, ChannelPassword, true, atoi(row[3]));
}
mysql_free_result(result);
}

View File

@ -305,6 +305,7 @@ public:
void SetHideMe(bool hm);
inline uint16 GetPort() const { return port; }
bool IsDead() const { return(dead); }
bool IsUnconscious() const { return ((cur_hp <= 0) ? true : false); }
inline bool IsLFP() { return LFP; }
void UpdateLFP();
@ -1169,6 +1170,7 @@ public:
std::string GetAccountFlag(std::string flag); float GetDamageMultiplier(SkillUseTypes);
void Consume(const Item_Struct *item, uint8 type, int16 slot, bool auto_consume);
void PlayMP3(const char* fname);
void ExpeditionSay(const char *str, int ExpID);
int mod_client_damage(int damage, SkillUseTypes skillinuse, int hand, const ItemInst* weapon, Mob* other);
bool mod_client_message(char* message, uint8 chan_num);
bool mod_can_increase_skill(SkillUseTypes skillid, Mob* against_who);
@ -1208,8 +1210,8 @@ protected:
Mob* bind_sight_target;
VERTEX aa_los_me;
VERTEX aa_los_them;
Map::Vertex aa_los_me;
Map::Vertex aa_los_them;
Mob *aa_los_them_mob;
bool los_status;
float aa_los_me_heading;

View File

@ -1328,7 +1328,7 @@ void Client::Handle_OP_AutoAttack(const EQApplicationPacket *app)
aa_los_them.y = aa_los_them_mob->GetY();
aa_los_them.z = aa_los_them_mob->GetZ();
los_status = CheckLosFN(aa_los_them_mob);
los_status_facing = aa_los_them_mob->InFrontMob(this, aa_los_them.x, aa_los_them.y);
los_status_facing = IsFacingMob(aa_los_them_mob);
}
else
{
@ -5421,6 +5421,12 @@ void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
action = 0;
}
// 1199 I don't have time for that now. etc
if (!tmp->CastToNPC()->IsMerchantOpen()) {
tmp->Say_StringID(MakeRandomInt(1199, 1202));
action = 0;
}
EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
@ -8554,10 +8560,10 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app)
}
else
{
VERTEX Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION);
VERTEX End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION);
Map::Vertex Start(GetX(), GetY(), GetZ() + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION);
Map::Vertex End(target->GetX(), target->GetY(), target->GetZ() + (target->GetSize() < 6.0 ? 6 : target->GetSize()) * HEAD_POSITION);
if(!zone->zonemap->LineIntersectsZone(Start, End, 1.0f, nullptr, nullptr) && zone->pathing->NoHazards(Start, End))
if(!zone->zonemap->LineIntersectsZone(Start, End, 1.0f, nullptr) && zone->pathing->NoHazards(Start, End))
{
points.resize(2);
points[0].x = Start.x;
@ -8598,7 +8604,7 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app)
bool LeadsToTeleporter = false;
VERTEX v = zone->pathing->GetPathNodeCoordinates(pathlist.back());
Map::Vertex v = zone->pathing->GetPathNodeCoordinates(pathlist.back());
p.x = v.x;
p.y = v.y;
@ -8618,7 +8624,7 @@ void Client::Handle_OP_FindPersonRequest(const EQApplicationPacket *app)
break;
}
VERTEX v = zone->pathing->GetPathNodeCoordinates((*Iterator), false);
Map::Vertex v = zone->pathing->GetPathNodeCoordinates((*Iterator), false);
p.x = v.x;
p.y = v.y;
p.z = v.z;
@ -8789,33 +8795,32 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) {
//if we zone in with invalid Z, fix it.
if (zone->zonemap != nullptr) {
//for whatever reason, LineIntersectsNode is giving better results than FindBestZ
NodeRef pnode;
VERTEX me;
Map::Vertex me;
me.x = GetX();
me.y = GetY();
me.z = GetZ() + (GetSize()==0.0?6:GetSize());
pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), me.x, me.y );
me.z = GetZ() + (GetSize() == 0.0 ? 6 : GetSize());
VERTEX hit;
VERTEX below_me(me);
below_me.z -= 500;
if(!zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr) || hit.z < -5000) {
Map::Vertex hit;
if (zone->zonemap->FindBestZ(me, &hit) == BEST_Z_INVALID)
{
#if EQDEBUG >= 5
LogFile->write(EQEMuLog::Debug, "Player %s started below the zone trying to fix! (%.3f, %.3f, %.3f)", GetName(), me.x, me.y, me.z);
#endif
//theres nothing below us... try to find something to stand on
me.z += 200; //arbitrary #
if(zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr)) {
if (zone->zonemap->FindBestZ(me, &hit) != BEST_Z_INVALID)
{
//+10 so they dont stick in the ground
SendTo(me.x, me.y, hit.z + 10);
m_pp.z = hit.z + 10;
} else {
}
else
{
//one more, desperate try
me.z += 2000;
if(zone->zonemap->LineIntersectsNode(pnode, me, below_me, &hit, nullptr)) {
//+10 so they dont stick in the ground
if (zone->zonemap->FindBestZ(me, &hit) != BEST_Z_INVALID)
{
//+10 so they dont stick in the ground
SendTo(me.x, me.y, hit.z + 10);
m_pp.z = hit.z + 10;
}
@ -8823,14 +8828,6 @@ bool Client::FinishConnState2(DBAsyncWork* dbaw) {
}
}
//m_pp.hunger_level = 6000;
//m_pp.thirst_level = 6000;
//aa_title = m_pp.aa_title;
//m_pp.timeplayed=64;
//m_pp.birthday=1057434792;
//m_pp.lastlogin=1057464792;
if (m_pp.gm && admin < minStatusToBeGM)
m_pp.gm = 0;
@ -12477,7 +12474,15 @@ void Client::Handle_OP_GuildCreate(const EQApplicationPacket *app)
//
char *GuildName = (char *)app->pBuffer;
#ifdef DARWIN
#if __DARWIN_C_LEVEL < 200809L
if (strlen(GuildName) > 60)
#else
if(strnlen(GuildName, 64) > 60)
#endif // __DARWIN_C_LEVEL
#else
if(strnlen(GuildName, 64) > 60)
#endif // DARWIN
{
Message(clientMessageError, "Guild name too long.");
return;
@ -12935,7 +12940,15 @@ void Client::Handle_OP_LFGuild(const EQApplicationPacket *app)
VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_PlayerToggle_Struct);
LFGuild_PlayerToggle_Struct *pts = (LFGuild_PlayerToggle_Struct *)app->pBuffer;
#ifdef DARWIN
#if __DARWIN_C_LEVEL < 200809L
if (strlen(pts->Comment) > 256)
#else
if(strnlen(pts->Comment, 256) > 256)
#endif // __DARWIN_C_LEVEL
#else
if(strnlen(pts->Comment, 256) > 256)
#endif // DARWIN
return;
ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(pts->Comment) + 38);
@ -12962,7 +12975,15 @@ void Client::Handle_OP_LFGuild(const EQApplicationPacket *app)
VERIFY_PACKET_LENGTH(OP_LFGuild, app, LFGuild_GuildToggle_Struct);
LFGuild_GuildToggle_Struct *gts = (LFGuild_GuildToggle_Struct *)app->pBuffer;
if(strnlen(gts->Comment, 256) > 256)
#ifdef DARWIN
#if __DARWIN_C_LEVEL < 200809L
if (strlen(gts->Comment) > 256)
#else
if(strnlen(gts->Comment, 256) > 256)
#endif // __DARWIN_C_LEVEL
#else
if(strnlen(gts->Comment, 256) > 256)
#endif // __DARWIN
return;
ServerPacket* pack = new ServerPacket(ServerOP_QueryServGeneric, strlen(GetName()) + strlen(gts->Comment) + strlen(guild_mgr.GetGuildName(GuildID())) + 43);

View File

@ -361,13 +361,13 @@ bool Client::Process() {
aa_los_them.z = aa_los_them_mob->GetZ();
los_status = CheckLosFN(auto_attack_target);
aa_los_me_heading = GetHeading();
los_status_facing = aa_los_them_mob->InFrontMob(this, aa_los_them.x, aa_los_them.y);
los_status_facing = IsFacingMob(aa_los_them_mob);
}
// If only our heading changes, we can skip the CheckLosFN call
// but above we still need to update los_status_facing
if (aa_los_me_heading != GetHeading()) {
aa_los_me_heading = GetHeading();
los_status_facing = aa_los_them_mob->InFrontMob(this, aa_los_them.x, aa_los_them.y);
los_status_facing = IsFacingMob(aa_los_them_mob);
}
}
else
@ -381,7 +381,7 @@ bool Client::Process() {
aa_los_them.y = aa_los_them_mob->GetY();
aa_los_them.z = aa_los_them_mob->GetZ();
los_status = CheckLosFN(auto_attack_target);
los_status_facing = aa_los_them_mob->InFrontMob(this, aa_los_them.x, aa_los_them.y);
los_status_facing = IsFacingMob(aa_los_them_mob);
}
if (!CombatRange(auto_attack_target))
@ -570,6 +570,9 @@ bool Client::Process() {
viral_timer_counter = 0;
}
if(projectile_timer.Check())
SpellProjectileEffect();
if(spellbonuses.GravityEffect == 1) {
if(gravity_timer.Check())
DoGravityEffect();

View File

@ -449,7 +449,11 @@ int command_init(void) {
command_add("questerrors", "Shows quest errors.", 100, command_questerrors) ||
command_add("enablerecipe", "[recipe_id] - Enables a recipe using the recipe id.", 80, command_enablerecipe) ||
command_add("disablerecipe", "[recipe_id] - Disables a recipe using the recipe id.", 80, command_disablerecipe) ||
command_add("npctype_cache", "[id] or all - Clears the npc type cache for either the id or all npcs.", 250, command_npctype_cache)
command_add("npctype_cache", "[id] or all - Clears the npc type cache for either the id or all npcs.", 250, command_npctype_cache) ||
command_add("merchant_open_shop", "Opens a merchants shop", 100, command_merchantopenshop) ||
command_add("open_shop", nullptr, 100, command_merchantopenshop) ||
command_add("merchant_close_shop", "Closes a merchant shop", 100, command_merchantcloseshop) ||
command_add("close_shop", nullptr, 100, command_merchantcloseshop)
)
{
command_deinit();
@ -2638,19 +2642,35 @@ void command_makepet(Client *c, const Seperator *sep)
void command_level(Client *c, const Seperator *sep)
{
uint16 level = atoi(sep->arg[1]);
if ((level <= 0) || ((level > RuleI(Character, MaxLevel)) && (c->Admin() < commandLevelAboveCap)) )
if ((level <= 0) || ((level > RuleI(Character, MaxLevel)) && (c->Admin() < commandLevelAboveCap))) {
c->Message(0, "Error: #Level: Invalid Level");
else if (c->Admin() < 100)
}
else if (c->Admin() < 100) {
c->SetLevel(level, true);
else if (!c->GetTarget())
#ifdef BOTS
if(RuleB(Bots, BotLevelsWithOwner))
Bot::LevelBotWithClient(c, level, true);
#endif
}
else if (!c->GetTarget()) {
c->Message(0, "Error: #Level: No target");
else
if (!c->GetTarget()->IsNPC() && ((c->Admin() < commandLevelNPCAboveCap) && (level > RuleI(Character, MaxLevel))))
}
else {
if (!c->GetTarget()->IsNPC() && ((c->Admin() < commandLevelNPCAboveCap) && (level > RuleI(Character, MaxLevel)))) {
c->Message(0, "Error: #Level: Invalid Level");
else
}
else {
c->GetTarget()->SetLevel(level, true);
if(c->GetTarget() && c->GetTarget()->IsClient())
c->GetTarget()->CastToClient()->SendLevelAppearance();
if(c->GetTarget()->IsClient()) {
c->GetTarget()->CastToClient()->SendLevelAppearance();
#ifdef BOTS
if(RuleB(Bots, BotLevelsWithOwner))
Bot::LevelBotWithClient(c->GetTarget()->CastToClient(), level, true);
#endif
}
}
}
}
void command_spawn(Client *c, const Seperator *sep)
@ -7467,8 +7487,8 @@ void command_path(Client *c, const Seperator *sep)
if(zone->zonemap)
{
VERTEX loc(px, py, pz);
best_z = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr);
Map::Vertex loc(px, py, pz);
best_z = zone->zonemap->FindBestZ(loc, nullptr);
}
else
{
@ -7494,8 +7514,8 @@ void command_path(Client *c, const Seperator *sep)
if(zone->zonemap)
{
VERTEX loc(px, py, pz);
best_z = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr);
Map::Vertex loc(px, py, pz);
best_z = zone->zonemap->FindBestZ(loc, nullptr);
}
else
{
@ -7627,8 +7647,8 @@ void command_path(Client *c, const Seperator *sep)
{
if(c && c->GetTarget())
{
if(zone->pathing->NoHazardsAccurate(VERTEX(c->GetX(),c->GetY(),c->GetZ()),
VERTEX(c->GetTarget()->GetX(),c->GetTarget()->GetY(),c->GetTarget()->GetZ())))
if (zone->pathing->NoHazardsAccurate(Map::Vertex(c->GetX(), c->GetY(), c->GetZ()),
Map::Vertex(c->GetTarget()->GetX(), c->GetTarget()->GetY(), c->GetTarget()->GetZ())))
{
c->Message(0, "No hazards.");
}
@ -7704,7 +7724,7 @@ void command_path(Client *c, const Seperator *sep)
{
Mob *m = c->GetTarget();
VERTEX Position(m->GetX(), m->GetY(), m->GetZ());
Map::Vertex Position(m->GetX(), m->GetY(), m->GetZ());
int Node = zone->pathing->FindNearestPathNode(Position);
@ -7837,35 +7857,19 @@ void command_bestz(Client *c, const Seperator *sep) {
return;
}
NodeRef pnode;
if(c->GetTarget()) {
pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), c->GetTarget()->GetX(), c->GetTarget()->GetY() );
} else {
pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), c->GetX(), c->GetY() );
}
if (pnode == NODE_NONE) {
c->Message(0,"Unable to find your node.");
return;
}
VERTEX me;
Map::Vertex me;
me.x = c->GetX();
me.y = c->GetY();
me.z = c->GetZ() + (c->GetSize()==0.0?6:c->GetSize()) * HEAD_POSITION;
VERTEX hit;
VERTEX bme(me);
Map::Vertex hit;
Map::Vertex bme(me);
bme.z -= 500;
float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, nullptr);
float best_z2 = -999990;
if(zone->zonemap->LineIntersectsNode(pnode, me, bme, &hit, nullptr)) {
best_z2 = hit.z;
}
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
{
c->Message(0,"Z is %.3f or %.3f at (%.3f, %.3f).", best_z, best_z2, me.x, me.y);
c->Message(0,"Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y);
}
else
{
@ -11481,3 +11485,26 @@ void command_npctype_cache(Client *c, const Seperator *sep)
c->Message(0, "#npctype_cache all");
}
}
void command_merchantopenshop(Client *c, const Seperator *sep)
{
Mob *merchant = c->GetTarget();
if (!merchant || merchant->GetClass() != MERCHANT) {
c->Message(0, "You must target a merchant to open their shop.");
return;
}
merchant->CastToNPC()->MerchantOpenShop();
}
void command_merchantcloseshop(Client *c, const Seperator *sep)
{
Mob *merchant = c->GetTarget();
if (!merchant || merchant->GetClass() != MERCHANT) {
c->Message(0, "You must target a merchant to close their shop.");
return;
}
merchant->CastToNPC()->MerchantCloseShop();
}

View File

@ -324,6 +324,8 @@ void command_enablerecipe(Client *c, const Seperator *sep);
void command_disablerecipe(Client *c, const Seperator *sep);
void command_showspellslist(Client *c, const Seperator *sep);
void command_npctype_cache(Client *c, const Seperator *sep);
void command_merchantopenshop(Client *c, const Seperator *sep);
void command_merchantcloseshop(Client *c, const Seperator *sep);
#ifdef EQPROFILE
void command_profiledump(Client *c, const Seperator *sep);

View File

@ -5,6 +5,7 @@
#include "../common/spdat.h"
#define HIGHEST_RESIST 9 //Max resist type value
#define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob.
/* solar: macros for IsAttackAllowed, IsBeneficialAllowed */
#define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC())
@ -344,6 +345,8 @@ struct StatBonuses {
int16 FrenziedDevastation; // base1= AArank(used) base2= chance increase spell criticals + all DD spells 2x mana.
uint16 AbsorbMagicAtt[2]; // 0 = magic rune value 1 = buff slot
uint16 MeleeRune[2]; // 0 = rune value 1 = buff slot
bool NegateIfCombat; // Bool Drop buff if cast or melee
int8 Screech; // -1 = Will be blocked if another Screech is +(1)
// AAs
int8 Packrat; //weight reduction for items, 1 point = 10%

View File

@ -55,9 +55,36 @@ int32 NPC::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) {
else
value -= target->GetFcDamageAmtIncoming(this, spell_id)/spells[spell_id].buffduration;
}
value += dmg*SpellFocusDMG/100;
if (AI_HasSpellsEffects()){
int16 chance = 0;
int ratio = 0;
if (spells[spell_id].buffduration == 0) {
chance += spellbonuses.CriticalSpellChance + spellbonuses.FrenziedDevastation;
if (chance && MakeRandomInt(1,100) <= chance){
ratio += spellbonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncNoStack;
value += (value*ratio)/100;
entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_BLAST, GetCleanName(), itoa(-value));
}
}
else {
chance += spellbonuses.CriticalDoTChance;
if (chance && MakeRandomInt(1,100) <= chance){
ratio += spellbonuses.DotCritDmgIncrease;
value += (value*ratio)/100;
}
}
}
return value;
}
@ -79,12 +106,12 @@ int32 Client::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) {
if (spell_id == SPELL_IMP_HARM_TOUCH) //Improved Harm Touch
value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch
int chance = RuleI(Spells, BaseCritChance);
int chance = RuleI(Spells, BaseCritChance); //Wizard base critical chance is 2% (Does not scale with level)
chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance;
chance += itembonuses.FrenziedDevastation + spellbonuses.FrenziedDevastation + aabonuses.FrenziedDevastation;
if (chance > 0){
if (chance > 0 || (GetClass() == WIZARD && GetLevel() >= RuleI(Spells, WizCritLevel))) {
int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals.
@ -99,7 +126,7 @@ int32 Client::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) {
}
else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (MakeRandomInt(1,100) <= RuleI(Spells, WizCritChance))) {
ratio = MakeRandomInt(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio.
ratio += MakeRandomInt(20,70); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed)
Critical = true;
}
@ -254,6 +281,21 @@ int32 NPC::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) {
value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id);
value += value*target->GetHealRate(spell_id, this)/100;
}
//Allow for critical heal chance if NPC is loading spell effect bonuses.
if (AI_HasSpellsEffects()){
if(spells[spell_id].buffduration < 1) {
if(spellbonuses.CriticalHealChance && (MakeRandomInt(0,99) < spellbonuses.CriticalHealChance)) {
value = value*2;
entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_HEAL, GetCleanName(), itoa(value));
}
}
else if(spellbonuses.CriticalHealOverTime && (MakeRandomInt(0,99) < spellbonuses.CriticalHealOverTime)) {
value = value*2;
}
}
return value;
}

View File

@ -1678,14 +1678,15 @@ XS(XS__toggle_spawn_event);
XS(XS__toggle_spawn_event)
{
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, reset_base)");
if (items != 4)
Perl_croak(aTHX_ "Usage: toggle_spawn_event(event_id, enabled?, strict, reset_base)");
uint32 event_id = (int)SvIV(ST(0));
bool enabled = ((int)SvIV(ST(1))) == 0?false:true;
bool reset_base = ((int)SvIV(ST(1))) == 0?false:true;
bool strict = ((int)SvIV(ST(2))) == 0?false:true;
bool reset_base = ((int)SvIV(ST(3))) == 0?false:true;
quest_manager.toggle_spawn_event(event_id, enabled, reset_base);
quest_manager.toggle_spawn_event(event_id, enabled, strict, reset_base);
XSRETURN_EMPTY;
}

View File

@ -2605,8 +2605,8 @@ void EntityList::FindPathsToAllNPCs()
auto it = npc_list.begin();
while (it != npc_list.end()) {
VERTEX Node0 = zone->pathing->GetPathNodeCoordinates(0, false);
VERTEX Dest(it->second->GetX(), it->second->GetY(), it->second->GetZ());
Map::Vertex Node0 = zone->pathing->GetPathNodeCoordinates(0, false);
Map::Vertex Dest(it->second->GetX(), it->second->GetY(), it->second->GetZ());
std::list<int> Route = zone->pathing->FindRoute(Node0, Dest);
if (Route.size() == 0)
printf("Unable to find a route to %s\n", it->second->GetName());
@ -3415,9 +3415,14 @@ void EntityList::ReloadAllClientsTaskState(int TaskID)
bool EntityList::IsMobInZone(Mob *who)
{
auto it = mob_list.find(who->GetID());
if (it != mob_list.end())
return who == it->second;
//We don't use mob_list.find(who) because this code needs to be able to handle dangling pointers for the quest code.
auto it = mob_list.begin();
while(it != mob_list.end()) {
if(it->second == who) {
return true;
}
++it;
}
return false;
}
@ -3605,9 +3610,9 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z,
if (zone->zonemap == nullptr)
return true;
VERTEX myloc;
VERTEX oloc;
VERTEX hit;
Map::Vertex myloc;
Map::Vertex oloc;
Map::Vertex hit;
myloc.x = cur_x;
myloc.y = cur_y;
@ -3620,9 +3625,7 @@ bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z,
if (myloc.x == oloc.x && myloc.y == oloc.y && myloc.z == oloc.z)
return true;
FACE *onhit;
if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit,&onhit))
if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit))
return true;
return false;
}

View File

@ -326,7 +326,18 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) {
}
else
Message(15, "Welcome to level %i!", check_level);
#ifdef BOTS
uint8 myoldlevel = GetLevel();
#endif
SetLevel(check_level);
#ifdef BOTS
if(RuleB(Bots, BotLevelsWithOwner))
// hack way of doing this..but, least invasive... (same criteria as gain level for sendlvlapp)
Bot::LevelBotWithClient(this, GetLevel(), (myoldlevel==check_level-1));
#endif
}
//If were at max level then stop gaining experience if we make it to the cap

View File

@ -156,11 +156,11 @@ void Mob::CalculateNewFearpoint()
{
int Node = zone->pathing->GetRandomPathNode();
VERTEX Loc = zone->pathing->GetPathNodeCoordinates(Node);
Map::Vertex Loc = zone->pathing->GetPathNodeCoordinates(Node);
++Loc.z;
VERTEX CurrentPosition(GetX(), GetY(), GetZ());
Map::Vertex CurrentPosition(GetX(), GetY(), GetZ());
std::list<int> Route = zone->pathing->FindRoute(CurrentPosition, Loc);
@ -209,429 +209,6 @@ void Mob::CalculateNewFearpoint()
}
}
//we need to start acting scared...
//old fear function, kept for ref.
/*void Mob::SetFeared(Mob *caster, uint32 duration, bool flee) {
//special args to stop fear
if(caster == nullptr && duration == 0) {
fear_state = fearStateNotFeared;
#ifdef FLEE_HP_RATIO
flee_mode = false;
#endif
safe_delete(fear_path_state);
return;
}
flee_mode = flee;
//fear dosent work without at least maps
if(zone->zonemap == nullptr) {
fear_state = fearStateStuck;
return; //just stand there
}
//if we are allready feared, and we are on a fear grid..
//then just stay happy on the grid...
if(fear_path_state != nullptr) {
if(fear_state != fearStateGrid) {
LogFile->write(EQEMuLog::Debug, "Umm... %s has a fear path state, but is not in a grid state. Wtf?", GetName());
fear_state = fearStateGrid;
}
return;
}
//try to run straight away from the caster
VERTEX hit, fear_vector;
if(FearTryStraight(caster, duration, flee, hit, fear_vector)) {
return;
}
//OK, so if we just run, we are going to hit something...
//now we have to think a little more.
//first, try to find a fear node that we can see.
if(zone->pathing != nullptr) {
fear_path_state = new MobFearState();
if(zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ())) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: found path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z);
#endif
//we found a fear node... were on our way..
cur_wp_x = fear_path_state->x;
cur_wp_y = fear_path_state->y;
cur_wp_z = fear_path_state->z;
fear_state = fearStateGrid;
return;
}
//we have failed to find a path, so we dont need this..
safe_delete(fear_path_state);
}
//if we cannot just run, and we cannot see any paths, then
//we will give one last ditch effort to find a legit path. We
//will run as far as we can away from the player, and hope we
//can see a path from there if not, we will start breaking rules
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: Hope run from (%.2f, %.2f, %.2f), hit at (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), hit.x, hit.y, hit.z);
#endif
//use the hit point - a little + a little Z as the first waypoint.
cur_wp_x = hit.x - fear_vector.x * 2;
cur_wp_y = hit.y - fear_vector.y * 2;
cur_wp_z = GetZ();
fear_state = fearStateRunning;
}
//old fear function, kept for ref.
bool Mob::FearTryStraight(Mob *caster, uint32 duration, bool flee, VERTEX &hit, VERTEX &fear_vector) {
//gotta have somebody to run from
if(caster == nullptr)
return(false);
//our goal is to run along this vector...
fear_vector.x = GetX() - caster->GetX();
fear_vector.y = GetY() - caster->GetY();
fear_vector.z = 0; //I dont see any reason to use Z
float mag = sqrtf(fear_vector.x*fear_vector.x + fear_vector.y*fear_vector.y);
fear_vector.x /= mag;
fear_vector.y /= mag;
//now see if we can just run without hitting anything...
VERTEX start, end;
start.x = GetX();
start.y = GetY();
start.z = GetZ() + 5.0; //raise up a little over small bumps
//distance moved per movement tic.
float distance = NPC_SPEED_MULTIPLIER * GetFearSpeed();
//times number of movement tics in the spell.
distance *= float(duration) / float(AImovement_duration);
end.x = start.x + fear_vector.x * distance;
end.y = start.y + fear_vector.y * distance;
end.z = start.z;
if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing Start: can run entire vector from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), end.x, end.y, end.z);
#endif
//no hit, we can run this whole vector.
cur_wp_x = end.x;
cur_wp_y = end.y;
cur_wp_z = GetZ();
fear_state = fearStateRunningForever;
return(true); //were done, nothing difficult needed.
}
return(false);
}
//old fear function, kept for ref.
void Mob::CalculateFearPosition() {
if(zone->zonemap == nullptr || fear_state == fearStateStuck) {
return; //just stand there
}
//This is the entire movement section, right here:
if (cur_wp_x != GetX() && cur_wp_y != GetY()) {
// not at waypoint yet, so keep moving
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true);
return;
}
//we have reached our waypoint, now what?
//figure out a new waypoint to run at...
if(fear_state == fearStateRunningForever) {
if(flee_mode) {
//a fleeing mob may run away again
VERTEX hit, fear_vector;
if(FearTryStraight(GetHateTop(), FLEE_RUN_DURATION, true, hit, fear_vector))
return; //we are running again
//else, we need to find a grid, so act like we were on a hope run
fear_state = fearStateRunning;
}
#ifndef FORCE_FEAR_TO_RUN
else {
//we were supposed to run forever, but we did not...
//should re-fear ourself or something??
fear_state = fearStateStuck;
return;
}
#endif
}
//first see if we are on a path. if so our life is easy
if(fear_state == fearStateGrid && fear_path_state) {
//assume that we have zone->pathing since we got to this state.
if(!zone->pathing->NextFearPath(fear_path_state)) {
//this is bad, we were on a path and now its giving us
//an error... we dont have a good way to deal with this
fear_state = fearStateStuck;
return;
}
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: on path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z);
#endif
//we found a fear node... were on our way..
cur_wp_x = fear_path_state->x;
cur_wp_y = fear_path_state->y;
cur_wp_z = fear_path_state->z;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true);
return;
}
//the only valid state left is fearStateRunning, where we try to
//find a grid once we reach our waypoint, which we have..
if(fear_state != fearStateRunning) {
//wtf... unknown state
LogFile->write(EQEMuLog::Debug, "Fear Pathing: Reached our fear waypoint, but we are in an unknown state %d... stopping.", fear_state);
fear_state = fearStateStuck;
return;
}
//we wanted to try to find a waypoint now, so lets try..
if(zone->pathing != nullptr) {
fear_path_state = new MobFearState();
if(zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ())) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: ran to find path, moving from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z);
#endif
//we found a fear node... were on our way..
cur_wp_x = fear_path_state->x;
cur_wp_y = fear_path_state->y;
cur_wp_z = fear_path_state->z;
fear_state = fearStateGrid;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true);
return;
}
//if we get here... all valid methods have failed
#ifdef FORCE_FEAR_TO_RUN
//ok, now we start making shit up
//for now, we will limit our bullshitting to ignoring LOS
//when finding a pathing node, we SHOULD always get something..
//do not force a path if we are fleeing
if(!flee_mode && zone->pathing->FindNearestFear(fear_path_state, GetX(), GetY(), GetZ(), false)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: Bullshit Path from (%.2f, %.2f, %.2f) to path node (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), fear_path_state->x, fear_path_state->y, fear_path_state->z);
#endif
//we found a fear node... were on our way..
cur_wp_x = fear_path_state->x;
cur_wp_y = fear_path_state->y;
cur_wp_z = fear_path_state->z;
fear_state = fearStateGrid;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetFearSpeed(), true);
return;
}
#endif //FORCE_FEAR_TO_RUN
//we have failed to find a path once again, so we dont need this..
safe_delete(fear_path_state);
}
//if we get HERE... then NOTHING worked... just stick
fear_state = fearStateStuck;
//end of function, everything else is #ifdef'd out
//}
//I dont wanna get rid of this right now because it was a lot of hard
//work to write... but it dosent work reliably, so oh well..
#ifdef OLD_FEAR_PATHING
/*
The idea...
try to run along fear vector.
If we can see along it, run
otherwise, try to walk up a hill along the same vector
then try to move along a wall along largest component of FV
if cant move, change stae to stuck.
once we know a place to run, use the waypoint code to do it
then if combat ends, we will reach the waypoint and
*/
/*
//first try our original fear vector again...
VERTEX start, end, hit, normalhit;
start.x = GetX() - fear_vector.x * 0.4;
start.y = GetY() - fear_vector.y * 0.4;
start.z = GetZ() + 6.0; //raise up a little over small bumps
end.x = start.x + fear_vector.x * 10;
end.y = start.y + fear_vector.y * 10;
end.z = start.z;
if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &normalhit, nullptr)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) normal run to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), end.x, end.y, end.z);
#endif
//we can run along this vector without hitting anything...
cur_wp_x = end.x;
cur_wp_y = end.y;
cur_wp_z = end.z - 6.0;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true);
return;
}
//see if we can make ANY useful progress along that vector
//first, adjust normalhit to back up a little bit
//so we dont run through the wall
normalhit.x -= 0.4 * fear_vector.x;
normalhit.y -= 0.4 * fear_vector.y;
float xd = normalhit.x - start.x;
if(xd < 0)
xd = 0 - xd;
float yd = normalhit.y - start.y;
if(yd < 0)
yd = 0 - yd;
//this 2 is arbitrary
if((xd+yd) > 2.0) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) small run to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), cur_wp_x, cur_wp_y, cur_wp_z);
#endif
cur_wp_x = normalhit.x;
cur_wp_y = normalhit.y;
cur_wp_z = GetZ();
//try and fix up the Z coord if possible
//not sure if this is worth it, since it prolly isnt up much
NodeRef c = zone->zonemap->SeekNode(zone->zonemap->GetRoot(), end.x, end.y);
if(c != NODE_NONE) {
cur_wp_z = zone->zonemap->FindBestZ(c, end, &hit, nullptr);
if(cur_wp_z < start.z)
cur_wp_z = end.z; //revert on error
}
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true);
return;
}
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) normal hit at (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), normalhit.x, normalhit.y, normalhit.z);
#endif
//if we get here, we cannot run along our normal vector...
//try up hill first
/*
while this uphill stuff works great in outdoor zones,
it totally breaks dungeons...
float speed = GetRunspeed();
end.x = start.x + fear_vector.x * speed;
end.y = start.y + fear_vector.y * speed;
end.z = start.z + speed + speed;
if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) up hill run to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), end.x, end.y, end.z);
#endif
//we can run along this vector without hitting anything...
cur_wp_x = end.x - 0.4 * fear_vector.x;
cur_wp_y = end.y - 0.4 * fear_vector.y;
cur_wp_z = end.z;
//try and fix up the Z coord if possible
//not sure if this is worth it, since it prolly isnt up much
NodeRef c = zone->zonemap->SeekNode(zone->zonemap->GetRoot(), end.x, end.y);
if(c != NODE_NONE) {
cur_wp_z = zone->zonemap->FindBestZ(c, end, &hit, nullptr);
if(cur_wp_z < start.z)
cur_wp_z = end.z; //revert on error
}
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true);
return;
}
*/
/*
//cant run along our vector at all....
//one last ditch effort... try to move to the side a little
//along the minor component of the fear vector.
//try it in one direction first...
if(fear_vector.x < fear_vector.y) {
end.x = start.x + fear_vector.x * 3;
end.y = start.y;
} else {
end.x = start.x;
end.y = start.y + fear_vector.y * 3;
}
end.z = start.z + 3; //a little lift as always
if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) strafe 1 to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), end.x, end.y, end.z);
#endif
//we can run along this vector without hitting anything...
cur_wp_x = end.x - 0.4 * fear_vector.x;
cur_wp_y = end.y - 0.4 * fear_vector.y;
cur_wp_z = end.z - 3;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true);
return;
}
//now the other...
if(fear_vector.x < fear_vector.y) {
end.x = start.x + fear_vector.x * 3;
end.y = start.y;
} else {
end.x = start.x;
end.y = start.y + fear_vector.y * 3;
}
end.z = start.z + 3; //a little lift as always
if(!zone->zonemap->LineIntersectsZone(start, end, 0.5, &hit, nullptr)) {
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) strafe 2 to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), end.x, end.y, end.z);
#endif
//we can run along this vector without hitting anything...
cur_wp_x = end.x - 0.4 * fear_vector.x;
cur_wp_y = end.y - 0.4 * fear_vector.y;
cur_wp_z = end.z - 3;
CalculateNewPosition2(cur_wp_x, cur_wp_y, cur_wp_z, GetRunspeed(), true);
return;
}
//if we get here... we have wasted enough CPU cycles
//just call it quits on fear pathing...
//send them to normalhit and then stop
cur_wp_x = normalhit.x;
cur_wp_y = normalhit.y;
cur_wp_z = GetZ();
fear_state = fearStateRunningToStick;
#ifdef FEAR_PATHING_DEBUG
LogFile->write(EQEMuLog::Debug, "Fear Pathing: From (%.2f, %.2f, %.2f) final move to (%.2f, %.2f, %.2f)",
GetX(), GetY(), GetZ(), normalhit.x, normalhit.y, normalhit.z);
#endif
#endif //OLD_FEAR_PATHING
}*/

View File

@ -225,7 +225,7 @@ bool Client::CanFish() {
return false;
}
if(zone->zonemap!=nullptr && zone->watermap != nullptr && RuleB(Watermap, CheckForWaterWhenFishing)) {
if(zone->zonemap != nullptr && zone->watermap != nullptr && RuleB(Watermap, CheckForWaterWhenFishing)) {
float RodX, RodY, RodZ;
// Tweak Rod and LineLength if required
const float RodLength = RuleR(Watermap, FishingRodLength);
@ -240,24 +240,22 @@ bool Client::CanFish() {
// Do BestZ to find where the line hanging from the rod intersects the water (if it is water).
// and go 1 unit into the water.
VERTEX dest;
Map::Vertex dest;
dest.x = RodX;
dest.y = RodY;
dest.z = z_pos+10;
NodeRef n = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), dest.x, dest.y);
if(n != NODE_NONE) {
RodZ = zone->zonemap->FindBestZ(n, dest, nullptr, nullptr) - 1;
bool in_lava = zone->watermap->InLava(RodX, RodY, RodZ);
bool in_water = zone->watermap->InWater(RodX, RodY, RodZ) || zone->watermap->InVWater(RodX, RodY, RodZ);
//Message(0, "Rod is at %4.3f, %4.3f, %4.3f, InWater says %d, InLava says %d", RodX, RodY, RodZ, in_water, in_lava);
if (in_lava) {
Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something?
return false;
}
if((!in_water) || (z_pos-RodZ)>LineLength) { //Didn't hit the water OR the water is too far below us
Message_StringID(MT_Skills, FISHING_LAND); //Trying to catch land sharks perhaps?
return false;
}
RodZ = zone->zonemap->FindBestZ(dest, nullptr) - 1;
bool in_lava = zone->watermap->InLava(RodX, RodY, RodZ);
bool in_water = zone->watermap->InWater(RodX, RodY, RodZ) || zone->watermap->InVWater(RodX, RodY, RodZ);
//Message(0, "Rod is at %4.3f, %4.3f, %4.3f, InWater says %d, InLava says %d", RodX, RodY, RodZ, in_water, in_lava);
if (in_lava) {
Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something?
return false;
}
if((!in_water) || (z_pos-RodZ)>LineLength) { //Didn't hit the water OR the water is too far below us
Message_StringID(MT_Skills, FISHING_LAND); //Trying to catch land sharks perhaps?
return false;
}
}
return true;

View File

@ -60,14 +60,16 @@ void HateList::Wipe()
while(iterator != list.end())
{
Mob* m = (*iterator)->ent;
parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), m, "0", 0);
//iterator
if(m)
{
parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), m, "0", 0);
if(m->IsClient())
m->CastToClient()->DecrementAggroCount();
}
delete (*iterator);
iterator = list.erase(iterator);
if(m->IsClient())
m->CastToClient()->DecrementAggroCount();
}
}
@ -203,6 +205,9 @@ void HateList::Add(Mob *ent, int32 in_hate, int32 in_dam, bool bFrenzy, bool iAd
bool HateList::RemoveEnt(Mob *ent)
{
if (!ent)
return false;
bool found = false;
auto iterator = list.begin();
@ -210,15 +215,18 @@ bool HateList::RemoveEnt(Mob *ent)
{
if((*iterator)->ent == ent)
{
if(ent)
parse->EventNPC(EVENT_HATE_LIST, owner->CastToNPC(), ent, "0", 0);
delete (*iterator);
iterator = list.erase(iterator);
found = true;
if(ent->IsClient())
if(ent && ent->IsClient())
ent->CastToClient()->DecrementAggroCount();
}
delete (*iterator);
iterator = list.erase(iterator);
}
else
++iterator;
}
@ -445,6 +453,17 @@ Mob *HateList::GetMostHate(){
Mob *HateList::GetRandom()
{
int count = list.size();
if(count == 0) //If we don't have any entries it'll crash getting a random 0, -1 position.
return NULL;
if(count == 1) //No need to do all that extra work if we only have one hate entry
{
if(*list.begin()) // Just in case tHateEntry is invalidated somehow...
return (*list.begin())->ent;
return NULL;
}
auto iterator = list.begin();
int random = MakeRandomInt(0, count - 1);
for (int i = 0; i < random; i++)

View File

@ -209,7 +209,7 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
if(item == nullptr) {
Message(13, "Item %u does not exist.", item_id);
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create an item with an invalid id.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, item->ID, aug1, aug2, aug3, aug4, aug5);
GetName(), account_name, item_id, aug1, aug2, aug3, aug4, aug5);
return false;
}
@ -228,14 +228,20 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
return false;
}
// This code is ready to implement once the item load code is changed to process the 'minstatus' field.
// Checking #iteminfo in-game verfies that item->MinStatus is set to '0' regardless of field value.
// An optional sql script will also need to be added, once this goes live, to allow changing of the min status.
// check to make sure we are a GM if the item is GM-only
/*
else if(item->gm && (this->Admin() < 100))
Message(13, "You are not a GM and can not summon this item.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only item with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5);
else if(item->MinStatus && ((this->Admin() < item->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) {
Message(13, "You are not a GM or do not have the status to summon this item.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only item with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u, MinStatus: %u)\n",
GetName(), account_name, this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, item->MinStatus);
return false;
}
*/
uint32 augments[MAX_AUGMENT_SLOTS] = { aug1, aug2, aug3, aug4, aug5 };
@ -276,12 +282,15 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
return false;
}
// Same as GM check above
// check to make sure we are a GM if the augment is GM-only
/*
else if(augtest->gm && (this->Admin() < 100)) {
Message(13, "You are not a GM and can not summon this augment.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only augment (Aug%i) with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u)\n",
GetName(), account_name, (iter + 1), this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5);
else if(augtest->MinStatus && ((this->Admin() < augtest->MinStatus) || (this->Admin() < RuleI(GM, MinStatusToSummonItem)))) {
Message(13, "You are not a GM or do not have the status to summon this augment.");
mlog(INVENTORY__ERROR, "Player %s on account %s attempted to create a GM-only augment (Aug%i) with a status of %i.\n(Item: %u, Aug1: %u, Aug2: %u, Aug3: %u, Aug4: %u, Aug5: %u, MinStatus: %u)\n",
GetName(), account_name, (iter + 1), this->Admin(), item->ID, aug1, aug2, aug3, aug4, aug5, item->MinStatus);
return false;
}

View File

@ -372,8 +372,8 @@ int lua_get_spawn_condition(const char *zone, uint32 instance_id, int condition_
return quest_manager.get_spawn_condition(zone, instance_id, condition_id);
}
void lua_toggle_spawn_event(int event_id, bool enable, bool reset) {
quest_manager.toggle_spawn_event(event_id, enable, reset);
void lua_toggle_spawn_event(int event_id, bool enable, bool strict, bool reset) {
quest_manager.toggle_spawn_event(event_id, enable, strict, reset);
}
void lua_summon_burried_player_corpse(uint32 char_id, float x, float y, float z, float h) {

View File

@ -408,7 +408,7 @@ void Lua_NPC::SetSpellFocusHeal(int focus) {
}
float Lua_NPC::GetSlowMitigation() {
Lua_Safe_Call_Real();
Lua_Safe_Call_Int();
return self->GetSlowMitigation();
}
@ -432,6 +432,16 @@ int Lua_NPC::GetScore() {
return self->GetScore();
}
void Lua_NPC::MerchantOpenShop() {
Lua_Safe_Call_Void();
self->MerchantOpenShop();
}
void Lua_NPC::MerchantCloseShop() {
Lua_Safe_Call_Void();
self->MerchantCloseShop();
}
luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
@ -516,11 +526,13 @@ luabind::scope lua_register_npc() {
.def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell)
.def("SetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusDMG)
.def("SetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusHeal)
.def("GetSlowMitigation", (float(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation)
.def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation)
.def("GetAttackSpeed", (float(Lua_NPC::*)(void))&Lua_NPC::GetAttackSpeed)
.def("GetAccuracyRating", (int(Lua_NPC::*)(void))&Lua_NPC::GetAccuracyRating)
.def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount)
.def("GetScore", (int(Lua_NPC::*)(void))&Lua_NPC::GetScore);
.def("GetScore", (int(Lua_NPC::*)(void))&Lua_NPC::GetScore)
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop);
}
#endif

View File

@ -112,7 +112,9 @@ public:
int GetAccuracyRating();
int GetSpawnKillCount();
int GetScore();
void MerchantOpenShop();
void MerchantCloseShop();
};
#endif
#endif
#endif

268
zone/map.cpp Normal file
View File

@ -0,0 +1,268 @@
#include "../common/debug.h"
#include "../common/MiscFunctions.h"
#include "map.h"
#include "RaycastMesh.h"
#include "zone.h"
#include <stdint.h>
#include <algorithm>
#include <locale>
#include <vector>
struct Map::impl
{
RaycastMesh *rm;
};
Map::Map() {
imp = nullptr;
}
Map::~Map() {
if(imp) {
imp->rm->release();
}
}
float Map::FindBestZ(Vertex &start, Vertex *result) const {
if (!imp)
return false;
Vertex tmp;
if(!result)
result = &tmp;
start.z += RuleI(Map, FindBestZHeightAdjust);
Vertex from(start.x, start.y, start.z);
Vertex to(start.x, start.y, BEST_Z_INVALID);
float hit_distance;
bool hit = false;
hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance);
if(hit) {
return result->z;
}
// Find nearest Z above us
to.z = -BEST_Z_INVALID;
hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance);
if (hit)
{
return result->z;
}
return BEST_Z_INVALID;
}
bool Map::LineIntersectsZone(Vertex start, Vertex end, float step, Vertex *result) const {
if(!imp)
return false;
return imp->rm->raycast((const RmReal*)&start, (const RmReal*)&end, (RmReal*)result, nullptr, nullptr);
}
bool Map::LineIntersectsZoneNoZLeaps(Vertex start, Vertex end, float step_mag, Vertex *result) const {
if (!imp)
return false;
float z = BEST_Z_INVALID;
Vertex step;
Vertex cur;
cur.x = start.x;
cur.y = start.y;
cur.z = start.z;
step.x = end.x - start.x;
step.y = end.y - start.y;
step.z = end.z - start.z;
float factor = step_mag / sqrt(step.x*step.x + step.y*step.y + step.z*step.z);
step.x *= factor;
step.y *= factor;
step.z *= factor;
int steps = 0;
if (step.x > 0 && step.x < 0.001f)
step.x = 0.001f;
if (step.y > 0 && step.y < 0.001f)
step.y = 0.001f;
if (step.z > 0 && step.z < 0.001f)
step.z = 0.001f;
if (step.x < 0 && step.x > -0.001f)
step.x = -0.001f;
if (step.y < 0 && step.y > -0.001f)
step.y = -0.001f;
if (step.z < 0 && step.z > -0.001f)
step.z = -0.001f;
//while we are not past end
//always do this once, even if start == end.
while(cur.x != end.x || cur.y != end.y || cur.z != end.z)
{
steps++;
Vertex me;
me.x = cur.x;
me.y = cur.y;
me.z = cur.z;
Vertex hit;
float best_z = FindBestZ(me, &hit);
float diff = best_z - z;
diff = diff < 0 ? -diff : diff;
if (z == -999999 || best_z == -999999 || diff < 12.0)
z = best_z;
else
return true;
//look at current location
if(LineIntersectsZone(start, end, step_mag, result))
{
return true;
}
//move 1 step
if (cur.x != end.x)
cur.x += step.x;
if (cur.y != end.y)
cur.y += step.y;
if (cur.z != end.z)
cur.z += step.z;
//watch for end conditions
if ( (cur.x > end.x && end.x >= start.x) || (cur.x < end.x && end.x <= start.x) || (step.x == 0) ) {
cur.x = end.x;
}
if ( (cur.y > end.y && end.y >= start.y) || (cur.y < end.y && end.y <= start.y) || (step.y == 0) ) {
cur.y = end.y;
}
if ( (cur.z > end.z && end.z >= start.z) || (cur.z < end.z && end.z < start.z) || (step.z == 0) ) {
cur.z = end.z;
}
}
//walked entire line and didnt run into anything...
return false;
}
bool Map::CheckLoS(Vertex myloc, Vertex oloc) const {
if(!imp)
return false;
return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr);
}
Map *Map::LoadMapFile(std::string file) {
std::string filename = MAP_DIR;
filename += "/";
std::transform(file.begin(), file.end(), file.begin(), ::tolower);
filename += file;
filename += ".map";
Map *m = new Map();
if (m->Load(filename)) {
return m;
}
delete m;
return nullptr;
}
bool Map::Load(std::string filename) {
FILE *f = fopen(filename.c_str(), "rb");
if(f) {
uint32_t version;
if(fread(&version, sizeof(version), 1, f) != 1) {
fclose(f);
return false;
}
if(version == 0x01000000) {
bool v = LoadV1(f);
fclose(f);
return v;
} else if(version == 0x02000000) {
bool v = LoadV2(f);
fclose(f);
return v;
} else {
fclose(f);
return false;
}
}
return false;
}
bool Map::LoadV1(FILE *f) {
uint32_t face_count;
uint16_t node_count;
uint32_t facelist_count;
if(fread(&face_count, sizeof(face_count), 1, f) != 1) {
return false;
}
if(fread(&node_count, sizeof(node_count), 1, f) != 1) {
return false;
}
if(fread(&facelist_count, sizeof(facelist_count), 1, f) != 1) {
return false;
}
std::vector<Vertex> verts;
std::vector<uint32_t> indices;
for(uint32_t i = 0; i < face_count; ++i) {
Vertex a;
Vertex b;
Vertex c;
float normals[4];
if(fread(&a, sizeof(Vertex), 1, f) != 1) {
return false;
}
if(fread(&b, sizeof(Vertex), 1, f) != 1) {
return false;
}
if(fread(&c, sizeof(Vertex), 1, f) != 1) {
return false;
}
if(fread(normals, sizeof(normals), 1, f) != 1) {
return false;
}
size_t sz = verts.size();
verts.push_back(a);
indices.push_back((uint32_t)sz);
verts.push_back(b);
indices.push_back((uint32_t)sz + 1);
verts.push_back(c);
indices.push_back((uint32_t)sz + 2);
}
if(imp) {
imp->rm->release();
imp->rm = nullptr;
} else {
imp = new impl;
}
imp->rm = createRaycastMesh((RmUint32)verts.size(), (const RmReal*)&verts[0], face_count, &indices[0]);
if(!imp->rm) {
delete imp;
imp = nullptr;
return false;
}
return true;
}
bool Map::LoadV2(FILE *f) {
return false;
}

View File

@ -1,5 +1,7 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
/*
EQEMu: Everquest Server Emulator
Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -9,182 +11,57 @@
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef MAP_H
#define MAP_H
#ifndef ZONE_MAP_H
#define ZONE_MAP_H
#include <stdio.h>
#include <string>
//this is the current version number to expect from the map header
#define MAP_VERSION 0x01000000
#define BEST_Z_INVALID -99999
#define BEST_Z_INVALID -999999
#pragma pack(1)
typedef struct _vertex{
// unsigned long order;
float x;
float y;
float z;
_vertex()
{
x = y = z = 0.0f;
};
_vertex(float ix, float iy, float iz)
{
x = ix;
y = iy;
z = iz;
}
bool operator==(const _vertex &v1) const
{
return((v1.x == x) && (v1.y == y) && (v1.z ==z));
}
}VERTEX, *PVERTEX;
typedef struct _face{
// unsigned long a, b, c; //vertexs
VERTEX a;
VERTEX b;
VERTEX c;
float nx, ny, nz, nd;
}FACE, *PFACE;
typedef struct _mapHeader {
uint32 version;
uint32 face_count;
uint16 node_count;
uint32 facelist_count;
} mapHeader;
/*
This is used for the recursive node structure
unsigned shorts are adequate because, worst case
even in a zone that is 6000x6000 with a small node
size of 30x30, there are only 40000 nodes.
quadrent definitions:
quad 1 (nodes[0]):
x>=0, y>=0
quad 2 (nodes[1]):
x<0, y>=0
quad 3 (nodes[2]):
x<0, y<0
quad 4 (nodes[3]):
x>=0, y<0
*/
enum { //node flags
nodeFinal = 0x01
//7 more bits if theres something to use them for...
};
typedef struct _nodeHeader {
//bounding box of this node
//there is no reason that these could not be unsigned
//shorts other than we have to compare them to floats
//all over the place, so they stay floats for now.
//changing it could save 8 bytes per node record (~320k for huge maps)
float minx;
float miny;
float maxx;
float maxy;
uint8 flags;
union {
uint16 nodes[4]; //index 0 means nullptr, not root
struct {
uint32 count;
uint32 offset;
} faces;
};
} nodeHeader, NODE, *PNODE;
#pragma pack()
//special value returned as 'not found'
#define NODE_NONE 65534
#define MAP_ROOT_NODE 0
typedef uint16 NodeRef;
/*typedef struct _node {
nodeHeader head;
unsigned int * pfaces;
char mask;
struct _node * node1, *node2, *node3, *node4;
}NODE, *PNODE;*/
class Map {
class Map
{
public:
static Map* LoadMapfile(const char* in_zonename, const char *directory = nullptr);
#pragma pack(1)
struct Vertex
{
Vertex() : x(0.0f), y(0.0f), z(0.0f) { }
Vertex(float _x, float _y, float _z) : x(_x), y(_y), z(_z) { }
~Vertex() { }
bool operator==(const Vertex &v) const
{
return((v.x == x) && (v.y == y) && (v.z == z));
}
float x;
float y;
float z;
};
#pragma pack()
Map();
~Map();
bool loadMap(FILE *fp);
//the result is always final, except special NODE_NONE
NodeRef SeekNode( NodeRef _node, float x, float y ) const;
//these are untested since rewrite:
int *SeekFace( NodeRef _node, float x, float y );
float GetFaceHeight( int _idx, float x, float y ) const;
bool LocWithinNode( NodeRef _node, float x, float y ) const;
//nodes to these functions must be final
bool LineIntersectsNode( NodeRef _node, VERTEX start, VERTEX end, VERTEX *result, FACE **on = nullptr) const;
bool LineIntersectsFace( PFACE cface, VERTEX start, VERTEX end, VERTEX *result) const;
float FindBestZ( NodeRef _node, VERTEX start, VERTEX *result, FACE **on = nullptr) const;
bool LineIntersectsZone(VERTEX start, VERTEX end, float step, VERTEX *result, FACE **on = nullptr) const;
// inline unsigned int GetVertexNumber( ) {return m_Vertex; }
inline uint32 GetFacesNumber( ) const { return m_Faces; }
// inline PVERTEX GetVertex( int _idx ) {return mFinalVertex + _idx; }
inline PFACE GetFace( int _idx) {return mFinalFaces + _idx; }
inline PFACE GetFaceFromlist( int _idx) {return &mFinalFaces[ mFaceLists[_idx] ]; }
inline NodeRef GetRoot( ) const { return MAP_ROOT_NODE; }
inline PNODE GetNode( NodeRef r ) { return( mNodes + r ); }
inline float GetMinX() const { return(_minx); }
inline float GetMaxX() const { return(_maxx); }
inline float GetMinY() const { return(_miny); }
inline float GetMaxY() const { return(_maxy); }
inline float GetMinZ() const { return(_minz); }
inline float GetMaxZ() const { return(_maxz); }
bool LineIntersectsZoneNoZLeaps(VERTEX start, VERTEX end, float step_mag, VERTEX *result, FACE **on);
float FindClosestZ(VERTEX p ) const;
float FindBestZ(Vertex &start, Vertex *result) const;
bool LineIntersectsZone(Vertex start, Vertex end, float step, Vertex *result) const;
bool LineIntersectsZoneNoZLeaps(Vertex start, Vertex end, float step_mag, Vertex *result) const;
bool CheckLoS(Vertex myloc, Vertex oloc) const;
bool Load(std::string filename);
static Map *LoadMapFile(std::string file);
private:
// unsigned long m_Vertex;
uint32 m_Faces;
uint32 m_Nodes;
uint32 m_FaceLists;
// PVERTEX mFinalVertex;
PFACE mFinalFaces;
PNODE mNodes;
uint32 *mFaceLists;
int mCandFaces[100];
float _minz, _maxz;
float _minx, _miny, _maxx, _maxy;
static void Normalize(VERTEX *p);
// void RecLoadNode( PNODE _node, FILE *l_f );
// void RecFreeNode( PNODE _node );
bool LoadV1(FILE *f);
bool LoadV2(FILE *f);
struct impl;
impl *imp;
};
#endif

View File

@ -276,6 +276,14 @@ Mob::Mob(const char* in_name,
casting_spell_inventory_slot = 0;
target = 0;
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; }
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; }
projectile_timer.Disable();
memset(&itembonuses, 0, sizeof(StatBonuses));
memset(&spellbonuses, 0, sizeof(StatBonuses));
memset(&aabonuses, 0, sizeof(StatBonuses));
@ -883,7 +891,8 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.invis = (invisible || hidden) ? 1 : 0; // TODO: load this before spawning players
ns->spawn.NPC = IsClient() ? 0 : 1;
ns->spawn.IsMercenary = IsMerc() ? 1 : 0;
ns->spawn.IsMercenary = (IsMerc() || no_target_hotkey) ? 1 : 0;
ns->spawn.petOwnerId = ownerid;
ns->spawn.haircolor = haircolor;
@ -1618,7 +1627,7 @@ void Mob::SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32
la->parm4 = parm4;
la->parm5 = parm5;
// Note that setting the b values to 0 will disable the related effect from the corresponding parameter.
// Setting the a value appears to have no affect at all.
// Setting the a value appears to have no affect at all.s
la->value1a = 1;
la->value1b = 1;
la->value2a = 1;
@ -2080,27 +2089,35 @@ void Mob::SetAttackTimer() {
}
bool Mob::CanThisClassDualWield(void) const
{
if (!IsClient()) {
bool Mob::CanThisClassDualWield(void) const {
if(!IsClient()) {
return(GetSkill(SkillDualWield) > 0);
} else {
const ItemInst* inst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY);
}
else if(CastToClient()->HasSkill(SkillDualWield)) {
const ItemInst* pinst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY);
const ItemInst* sinst = CastToClient()->GetInv().GetItem(SLOT_SECONDARY);
// 2HS, 2HB, or 2HP
if (inst && inst->IsType(ItemClassCommon)) {
const Item_Struct* item = inst->GetItem();
if ((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing))
if(pinst && pinst->IsWeapon()) {
const Item_Struct* item = pinst->GetItem();
if((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing))
return false;
} else {
//No weapon in hand... using hand-to-hand...
//only monks and beastlords? can dual wield their fists.
if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) {
return false;
}
}
return (CastToClient()->HasSkill(SkillDualWield)); // No skill = no chance
// OffHand Weapon
if(sinst && !sinst->IsWeapon())
return false;
// Dual-Wielding Empty Fists
if(!pinst && !sinst)
if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM)
return false;
return true;
}
return false;
}
bool Mob::CanThisClassDoubleAttack(void) const
@ -2450,13 +2467,18 @@ bool Mob::RemoveFromHateList(Mob* mob)
return bFound;
}
void Mob::WipeHateList()
{
if(IsEngaged())
{
hate_list.Wipe();
AI_Event_NoLongerEngaged();
}
hate_list.Wipe();
else
{
hate_list.Wipe();
}
}
uint32 Mob::RandomTimer(int min,int max) {
@ -2927,22 +2949,17 @@ void Mob::SetTarget(Mob* mob) {
float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
{
float ret = -999999;
if (zone->zonemap != 0)
if (zone->zonemap != nullptr)
{
NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y );
if (pnode != NODE_NONE)
Map::Vertex me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
Map::Vertex hit;
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
{
VERTEX me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
VERTEX hit;
FACE *onhit;
float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit);
if (best_z != -999999)
{
ret = best_z;
}
ret = best_z;
}
}
return ret;
@ -2954,20 +2971,15 @@ float Mob::GetGroundZ(float new_x, float new_y, float z_offset)
float ret = -999999;
if (zone->zonemap != 0)
{
NodeRef pnode = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), new_x, new_y );
if (pnode != NODE_NONE)
Map::Vertex me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
Map::Vertex hit;
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
{
VERTEX me;
me.x = new_x;
me.y = new_y;
me.z = z_pos+z_offset;
VERTEX hit;
FACE *onhit;
float best_z = zone->zonemap->FindBestZ(pnode, me, &hit, &onhit);
if (best_z != -999999)
{
ret = best_z;
}
ret = best_z;
}
}
return ret;
@ -4128,10 +4140,10 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id)
for (int i = 0; i < EFFECT_COUNT; i++) {
if (spells[spell_id].effectid[i] == SE_SpellOnKill2)
{
if (spells[spell_id].max[i] <= level)
if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level)
{
if(MakeRandomInt(0,99) < spells[spell_id].base[i])
SpellFinished(spells[spell_id].base2[i], this);
SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff);
}
}
}
@ -4144,19 +4156,19 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id)
// Allow to check AA, items and buffs in all cases. Base2 = Spell to fire | Base1 = % chance | Base3 = min level
for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) {
if(aabonuses.SpellOnKill[i] && (level >= aabonuses.SpellOnKill[i + 2])) {
if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) {
if(MakeRandomInt(0, 99) < static_cast<int>(aabonuses.SpellOnKill[i + 1]))
SpellFinished(aabonuses.SpellOnKill[i], this);
SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff);
}
if(itembonuses.SpellOnKill[i] && (level >= itembonuses.SpellOnKill[i + 2])){
if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){
if(MakeRandomInt(0, 99) < static_cast<int>(itembonuses.SpellOnKill[i + 1]))
SpellFinished(itembonuses.SpellOnKill[i], this);
SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff);
}
if(spellbonuses.SpellOnKill[i] && (level >= spellbonuses.SpellOnKill[i + 2])) {
if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) {
if(MakeRandomInt(0, 99) < static_cast<int>(spellbonuses.SpellOnKill[i + 1]))
SpellFinished(spellbonuses.SpellOnKill[i], this);
SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff);
}
}
@ -4171,21 +4183,21 @@ bool Mob::TrySpellOnDeath()
return false;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) {
if(IsClient() && aabonuses.SpellOnDeath[i]) {
if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) {
if(MakeRandomInt(0, 99) < static_cast<int>(aabonuses.SpellOnDeath[i + 1])) {
SpellFinished(aabonuses.SpellOnDeath[i], this);
SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff);
}
}
if(itembonuses.SpellOnDeath[i]) {
if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) {
if(MakeRandomInt(0, 99) < static_cast<int>(itembonuses.SpellOnDeath[i + 1])) {
SpellFinished(itembonuses.SpellOnDeath[i], this);
SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff);
}
}
if(spellbonuses.SpellOnDeath[i]) {
if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) {
if(MakeRandomInt(0, 99) < static_cast<int>(spellbonuses.SpellOnDeath[i + 1])) {
SpellFinished(spellbonuses.SpellOnDeath[i], this);
SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff);
}
}
}
@ -4353,6 +4365,49 @@ bool Mob::TryReflectSpell(uint32 spell_id)
return false;
}
void Mob::SpellProjectileEffect()
{
bool time_disable = false;
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (projectile_increment[i] == 0){
continue;
}
Mob* target = entity_list.GetMobID(projectile_target_id[i]);
float dist = 0;
if (target)
dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]);
int increment_end = 0;
increment_end = (dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms
if (increment_end <= projectile_increment[i]){
if (target && IsValidSpell(projectile_spell_id[i]))
SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true);
projectile_spell_id[i] = 0;
projectile_target_id[i] = 0;
projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0;
projectile_increment[i] = 0;
time_disable = true;
}
else {
projectile_increment[i]++;
time_disable = false;
}
}
if (time_disable)
projectile_timer.Disable();
}
void Mob::DoGravityEffect()
{
Mob *caster = nullptr;
@ -4586,33 +4641,21 @@ void Mob::CastOnNumHitFade(uint32 spell_id)
}
}
int Mob::SlowMitigation(bool slow_msg, Mob *caster, int slow_value)
void Mob::SlowMitigation(Mob* caster)
{
float int_slow_mitigation = slow_mitigation * 100.0f;
if (int_slow_mitigation > 100.0f)
return 0;
if (slow_msg)
if (GetSlowMitigation() && caster && caster->IsClient())
{
if (caster && caster->IsClient())
{
if ((int_slow_mitigation > 0.0f) && (int_slow_mitigation < 26.0f))
caster->Message(262, "Your spell was mostly successful");
if ((GetSlowMitigation() > 0) && (GetSlowMitigation() < 26))
caster->Message_StringID(MT_SpellFailure, SLOW_MOSTLY_SUCCESSFUL);
else if ((int_slow_mitigation >= 26.0f) && (int_slow_mitigation < 74.0f))
caster->Message(262, "Your spell was partially successful");
else if ((GetSlowMitigation() >= 26) && (GetSlowMitigation() < 74))
caster->Message_StringID(MT_SpellFailure, SLOW_PARTIALLY_SUCCESSFUL);
else if ((int_slow_mitigation >= 74.0f) && (int_slow_mitigation < 101.0f))
caster->Message(262, "Your spell was slightly successful");
}
return 0;
}
else if ((GetSlowMitigation() >= 74) && (GetSlowMitigation() < 101))
caster->Message_StringID(MT_SpellFailure, SLOW_SLIGHTLY_SUCCESSFUL);
else
{
slow_value -= (slow_value * static_cast<int>(int_slow_mitigation) / 100);
return slow_value;
else if (GetSlowMitigation() > 100)
caster->Message_StringID(MT_SpellFailure, SPELL_OPPOSITE_EFFECT);
}
}
@ -5003,3 +5046,54 @@ void Mob::ProcessSpecialAbilities(const std::string str) {
}
}
}
// derived from client to keep these functions more consistent
// if anything seems weird, blame SoE
bool Mob::IsFacingMob(Mob *other)
{
if (!other)
return false;
float angle = HeadingAngleToMob(other);
// what the client uses appears to be 2x our internal heading
float heading = GetHeading() * 2.0;
if (angle > 472.0 && heading < 40.0)
angle = heading;
if (angle < 40.0 && heading > 472.0)
angle = heading;
if (fabs(angle - heading) <= 80.0)
return true;
return false;
}
// All numbers derived from the client
float Mob::HeadingAngleToMob(Mob *other)
{
float mob_x = other->GetX();
float mob_y = other->GetY();
float this_x = GetX();
float this_y = GetY();
float y_diff = fabs(this_y - mob_y);
float x_diff = fabs(this_x - mob_x);
if (y_diff < 0.0000009999999974752427)
y_diff = 0.0000009999999974752427;
float angle = atan2(x_diff, y_diff) * 180.0 * 0.3183099014828645; // angle, nice "pi"
// return the right thing based on relative quadrant
// I'm sure this could be improved for readability, but whatever
if (this_y >= mob_y) {
if (mob_x >= this_x)
return (90.0 - angle + 90.0) * 511.5 * 0.0027777778;
if (mob_x <= this_x)
return (angle + 180.0) * 511.5 * 0.0027777778;
}
if (this_y > mob_y || mob_x > this_x)
return angle * 511.5 * 0.0027777778;
else
return (90.0 - angle + 270.0) * 511.5 * 0.0027777778;
}

View File

@ -125,6 +125,8 @@ public:
// less than 56 is in front, greater than 56 is usually where the client generates the messages
inline bool InFrontMob(Mob *other = 0, float ourx = 0.0f, float oury = 0.0f) const
{ return (!other || other == this) ? true : MobAngle(other, ourx, oury) < 56.0f; }
bool IsFacingMob(Mob *other); // kind of does the same as InFrontMob, but derived from client
float HeadingAngleToMob(Mob *other); // to keep consistent with client generated messages
virtual void RangedAttack(Mob* other) { }
virtual void ThrowingAttack(Mob* other) { }
uint16 GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg);
@ -161,7 +163,7 @@ public:
virtual void WearChange(uint8 material_slot, uint16 texture, uint32 color);
void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone);
void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0,
float angle = 0, float tilt = 0, float arc = 0);
float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr);
void ChangeSize(float in_size, bool bNoRestriction = false);
inline uint8 SeeInvisible() const { return see_invis; }
inline bool SeeInvisibleUndead() const { return see_invis_undead; }
@ -170,7 +172,7 @@ public:
bool IsInvisible(Mob* other = 0) const;
void SetInvisible(uint8 state);
bool AttackAnimation(SkillUseTypes &skillinuse, int Hand, const ItemInst* weapon);
//Song
bool UseBardSpellLogic(uint16 spell_id = 0xffff, int slot = -1);
bool ApplyNextBardPulse(uint16 spell_id, Mob *spell_target, uint16 slot);
@ -182,7 +184,8 @@ public:
bool IsBeneficialAllowed(Mob *target);
virtual int GetCasterLevel(uint16 spell_id);
void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0,
bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1);
bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1,
bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0);
void NegateSpellsBonuses(uint16 spell_id);
virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false) { return range;}
virtual int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr) { return value; }
@ -192,6 +195,7 @@ public:
virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime);
float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false,
int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false);
int ResistPhysical(int level_diff, uint8 caster_level);
uint16 GetSpecializeSkillValue(uint16 spell_id) const;
void SendSpellBarDisable();
void SendSpellBarEnable(uint16 spellid);
@ -222,6 +226,8 @@ public:
uint16 CastingSpellID() const { return casting_spell_id; }
bool DoCastingChecks();
bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier);
void SpellProjectileEffect();
bool TrySpellProjectile(Mob* spell_target, uint16 spell_id);
//Buff
void BuffProcess();
@ -337,6 +343,7 @@ public:
inline virtual int16 GetPR() const { return PR + itembonuses.PR + spellbonuses.PR; }
inline virtual int16 GetCR() const { return CR + itembonuses.CR + spellbonuses.CR; }
inline virtual int16 GetCorrup() const { return Corrup + itembonuses.Corrup + spellbonuses.Corrup; }
inline virtual int16 GetPhR() const { return PhR; }
inline StatBonuses GetItemBonuses() const { return itembonuses; }
inline StatBonuses GetSpellBonuses() const { return spellbonuses; }
inline StatBonuses GetAABonuses() const { return aabonuses; }
@ -410,7 +417,7 @@ public:
void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu);
void SendPosition();
void SetFlyMode(uint8 flymode);
inline void Teleport(VERTEX NewPosition) { x_pos = NewPosition.x; y_pos = NewPosition.y;
inline void Teleport(Map::Vertex NewPosition) { x_pos = NewPosition.x; y_pos = NewPosition.y;
z_pos = NewPosition.z; };
//AI
@ -440,7 +447,6 @@ public:
void ClearFeignMemory();
void PrintHateListToClient(Client *who) { hate_list.PrintToClient(who); }
std::list<tHateEntry*>& GetHateList() { return hate_list.GetHateList(); }
bool CheckLos(Mob* other);
bool CheckLosFN(Mob* other);
bool CheckLosFN(float posX, float posY, float posZ, float mobSize);
inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); }
@ -496,6 +502,7 @@ public:
bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN);
bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false);
bool HasProcs() const;
bool IsCombatProc(uint16 spell_id);
//Logging
bool IsLoggingEnabled() const { return(logging_enabled); }
@ -576,7 +583,7 @@ public:
void CastOnCurer(uint32 spell_id);
void CastOnCure(uint32 spell_id);
void CastOnNumHitFade(uint32 spell_id);
int SlowMitigation(bool slow_msg=false, Mob *caster = nullptr,int slow_value = 0);
void SlowMitigation(Mob* caster);
int16 GetCritDmgMob(uint16 skill);
int16 GetMeleeDamageMod_SE(uint16 skill);
int16 GetMeleeMinDamageMod_SE(uint16 skill);
@ -594,6 +601,7 @@ public:
bool PassCastRestriction(bool UseCastRestriction = true, int16 value = 0, bool IsDamage = true);
bool ImprovedTaunt();
bool TryRootFadeByDamage(int buffslot, Mob* attacker);
int16 GetSlowMitigation() const {return slow_mitigation;}
void ModSkillDmgTaken(SkillUseTypes skill_num, int value);
int16 GetModSkillDmgTaken(const SkillUseTypes skill_num);
@ -915,6 +923,7 @@ protected:
int16 DR;
int16 PR;
int16 Corrup;
int16 PhR;
bool moving;
int targeted;
bool findable;
@ -940,6 +949,7 @@ protected:
int16 petpower;
uint32 follow;
uint32 follow_dist;
bool no_target_hotkey;
uint8 gender;
uint16 race;
@ -983,7 +993,7 @@ protected:
bool HasDied();
void CalculateNewFearpoint();
float FindGroundZ(float new_x, float new_y, float z_offset=0.0);
VERTEX UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached);
Map::Vertex UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached);
void PrintRoute();
virtual float GetSympatheticProcChances(float &ProcBonus, float &ProcChance, int32 cast_time, int16 ProcRateMod);
@ -1017,7 +1027,7 @@ protected:
Timer attack_dw_timer;
Timer ranged_timer;
float attack_speed; //% increase/decrease in attack speed (not haste)
float slow_mitigation; // Allows for a slow mitigation based on a % in decimal form. IE, 1 = 100% mitigation, .5 is 50%
float slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%)
Timer tic_timer;
Timer mana_timer;
@ -1040,6 +1050,12 @@ protected:
uint8 bardsong_slot;
uint32 bardsong_target_id;
Timer projectile_timer;
uint32 projectile_spell_id[MAX_SPELL_PROJECTILE];
uint16 projectile_target_id[MAX_SPELL_PROJECTILE];
uint8 projectile_increment[MAX_SPELL_PROJECTILE];
float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE];
float rewind_x;
float rewind_y;
float rewind_z;
@ -1146,8 +1162,8 @@ protected:
// Pathing
//
VERTEX PathingDestination;
VERTEX PathingLastPosition;
Map::Vertex PathingDestination;
Map::Vertex PathingLastPosition;
int PathingLoopCount;
int PathingLastNodeVisited;
std::list<int> Route;

View File

@ -447,7 +447,7 @@ int main(int argc, char** argv) {
if (InterserverTimer.Check()) {
InterserverTimer.Start();
database.ping();
AsyncLoadVariables(dbasync, &database);
// AsyncLoadVariables(dbasync, &database);
entity_list.UpdateWho();
if (worldserver.TryReconnect() && (!worldserver.Connected()))
worldserver.AsyncConnect();

View File

@ -158,6 +158,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float
FR = d->FR;
PR = d->PR;
Corrup = d->Corrup;
PhR = d->PhR;
STR = d->STR;
STA = d->STA;
@ -199,6 +200,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float
SetMana(GetMaxMana());
MerchantType = d->merchanttype;
merchant_open = GetClass() == MERCHANT;
adventure_template_id = d->adventure_template;
org_x = x;
org_y = y;
@ -222,11 +224,14 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float
p_depop = false;
loottable_id = d->loottable_id;
no_target_hotkey = d->no_target_hotkey;
primary_faction = 0;
SetNPCFactionID(d->npc_faction_id);
npc_spells_id = 0;
HasAISpell = false;
HasAISpellEffects = false;
if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs))
{
@ -658,6 +663,9 @@ bool NPC::Process()
viral_timer_counter = 0;
}
if(projectile_timer.Check())
SpellProjectileEffect();
if(spellbonuses.GravityEffect == 1) {
if(gravity_timer.Check())
DoGravityEffect();
@ -1714,6 +1722,22 @@ bool Mob::HasNPCSpecialAtk(const char* parse) {
void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
{
Mob::FillSpawnStruct(ns, ForWho);
//Basic settings to make sure swarm pets work properly.
if (GetSwarmOwner()) {
Client *c = entity_list.GetClientByID(GetSwarmOwner());
if(c) {
SetAllowBeneficial(1); //Allow client cast swarm pets to be heal/buffed.
//This is a hack to allow CLIENT swarm pets NOT to be targeted with F8. Warning: Will turn name 'Yellow'!
if (RuleB(Pets, SwarmPetNotTargetableWithHotKey))
ns->spawn.IsMercenary = 1;
}
//NPC cast swarm pets should still be targetable with F8.
else
ns->spawn.IsMercenary = 0;
}
//Not recommended if using above (However, this will work better on older clients).
if (RuleB(Pets, UnTargetableSwarmPet)) {
if(GetOwnerID() || GetSwarmOwner()) {
ns->spawn.is_pet = 1;
@ -1862,6 +1886,12 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue)
return;
}
if(id == "PhR")
{
PhR = atoi(val.c_str());
return;
}
if(id == "runspeed")
{
runspeed = (float)atof(val.c_str());
@ -1975,7 +2005,7 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue)
if(id == "slow_mitigation")
{
slow_mitigation = atof(val.c_str());
slow_mitigation = atoi(val.c_str());
return;
}
if(id == "loottable_id")
@ -2058,6 +2088,8 @@ void NPC::CalcNPCResists() {
PR = (GetLevel() * 11)/10;
if (!Corrup)
Corrup = 15;
if (!PhR)
PhR = 10;
return;
}

View File

@ -66,6 +66,13 @@ struct AISpells_Struct {
int16 resist_adjust;
};
struct AISpellsEffects_Struct {
uint16 spelleffectid;
int32 base;
int32 limit;
int32 max;
};
class AA_SwarmPetInfo;
class NPC : public Mob
@ -96,8 +103,11 @@ public:
virtual void AI_Stop();
void AI_DoMovement();
bool AI_AddNPCSpells(uint32 iDBSpellsID);
bool AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID);
virtual bool AI_EngagedCastCheck();
bool AI_HasSpells() { return HasAISpell; }
bool AI_HasSpellsEffects() { return HasAISpellEffects; }
void ApplyAISpellEffects(StatBonuses* newbon);
virtual bool AI_PursueCastCheck();
virtual bool AI_IdleCastCheck();
@ -209,6 +219,10 @@ public:
void SetSecSkill(uint8 skill_type) { sec_melee_type = skill_type; }
uint32 MerchantType;
bool merchant_open;
inline void MerchantOpenShop() { merchant_open = true; }
inline void MerchantCloseShop() { merchant_open = false; }
inline bool IsMerchantOpen() { return merchant_open; }
void Depop(bool StartSpawnTimer = false);
void Stun(int duration);
void UnStun();
@ -229,7 +243,7 @@ public:
uint32 GetMaxDMG() const {return max_dmg;}
uint32 GetMinDMG() const {return min_dmg;}
float GetSlowMitigation() const {return slow_mitigation;}
int16 GetSlowMitigation() const {return slow_mitigation;}
float GetAttackSpeed() const {return attack_speed;}
bool IsAnimal() const { return(bodytype == BT_Animal); }
uint16 GetPetSpellID() const {return pet_spell_id;}
@ -241,6 +255,7 @@ public:
void AddLootDrop(const Item_Struct*dbitem, ItemList* itemlistconst, int16 charges, uint8 minlevel, uint8 maxlevel, bool equipit, bool wearchange = false);
virtual void DoClassAttacks(Mob *target);
void CheckSignal();
inline bool IsTargetableWithHotkey() const { return no_target_hotkey; }
//waypoint crap
int GetMaxWp() const { return max_wp; }
@ -284,6 +299,7 @@ public:
inline void GiveNPCTypeData(NPCType *ours) { NPCTypedata_ours = ours; }
inline const uint32 GetNPCSpellsID() const { return npc_spells_id; }
inline const uint32 GetNPCSpellsEffectsID() const { return npc_spells_effects_id; }
ItemList itemlist; //kathgar - why is this public? Doing other things or I would check the code
@ -334,6 +350,7 @@ public:
uint32 GetAdventureTemplate() const { return adventure_template_id; }
void AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint16 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust);
void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max);
void RemoveSpellFromNPCList(int16 spell_id);
Timer *GetRefaceTimer() const { return reface_timer; }
const uint32 GetAltCurrencyType() const { return NPCTypedata->alt_currency_type; }
@ -395,8 +412,11 @@ protected:
bool HasAISpell;
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
uint32 npc_spells_effects_id;
std::vector<AISpellsEffects_Struct> AIspellsEffects;
bool HasAISpellEffects;
uint32 max_dmg;
uint32 min_dmg;
int32 accuracy_rating;

View File

@ -21,7 +21,7 @@
extern Zone *zone;
float VertexDistance(VERTEX a, VERTEX b)
float VertexDistance(Map::Vertex a, Map::Vertex b)
{
float xdist = a.x - b.x;
float ydist = a.y - b.y;
@ -29,7 +29,7 @@ float VertexDistance(VERTEX a, VERTEX b)
return sqrtf(xdist * xdist + ydist * ydist + zdist * zdist);
}
float VertexDistanceNoRoot(VERTEX a, VERTEX b)
float VertexDistanceNoRoot(Map::Vertex a, Map::Vertex b)
{
float xdist = a.x - b.x;
float ydist = a.y - b.y;
@ -187,9 +187,9 @@ void PathManager::PrintPathing()
}
}
VERTEX PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ)
Map::Vertex PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ)
{
VERTEX Result;
Map::Vertex Result;
if(NodeNumber < Head.PathNodeCount)
{
@ -335,12 +335,12 @@ std::list<int> PathManager::FindRoute(int startID, int endID)
}
bool CheckLOSBetweenPoints(VERTEX start, VERTEX end) {
bool CheckLOSBetweenPoints(Map::Vertex start, Map::Vertex end) {
VERTEX hit;
FACE *face;
Map::Vertex hit;
if((zone->zonemap) && (zone->zonemap->LineIntersectsZone(start, end, 1, &hit, &face))) return false;
if((zone->zonemap) && (zone->zonemap->LineIntersectsZone(start, end, 1, &hit)))
return false;
return true;
}
@ -350,7 +350,7 @@ bool SortPathNodesByDistance(PathNodeSortStruct n1, PathNodeSortStruct n2)
return n1.Distance < n2.Distance;
}
std::list<int> PathManager::FindRoute(VERTEX Start, VERTEX End)
std::list<int> PathManager::FindRoute(Map::Vertex Start, Map::Vertex End)
{
_log(PATHING__DEBUG, "FindRoute(%8.3f, %8.3f, %8.3f, %8.3f, %8.3f, %8.3f)", Start.x, Start.y, Start.z, End.x, End.y, End.z);
@ -388,7 +388,7 @@ std::list<int> PathManager::FindRoute(VERTEX Start, VERTEX End)
{
_log(PATHING__DEBUG, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id);
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
{
ClosestPathNodeToStart = (*Iterator).id;
break;
@ -429,7 +429,7 @@ std::list<int> PathManager::FindRoute(VERTEX Start, VERTEX End)
End.x, End.y, End.z,
PathNodes[(*Iterator).id].v.x, PathNodes[(*Iterator).id].v.y, PathNodes[(*Iterator).id].v.z);
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
{
ClosestPathNodeToEnd = (*Iterator).id;
break;
@ -469,7 +469,7 @@ std::list<int> PathManager::FindRoute(VERTEX Start, VERTEX End)
if((*Second) < 0)
break;
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Second)].v, 1.0f, nullptr, nullptr)
if(!zone->zonemap->LineIntersectsZone(Start, PathNodes[(*Second)].v, 1.0f, nullptr)
&& zone->pathing->NoHazards(Start, PathNodes[(*Second)].v))
{
noderoute.erase(First);
@ -502,7 +502,7 @@ std::list<int> PathManager::FindRoute(VERTEX Start, VERTEX End)
if((*Second) < 0)
break;
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Second)].v, 1.0f, nullptr, nullptr)
if(!zone->zonemap->LineIntersectsZone(End, PathNodes[(*Second)].v, 1.0f, nullptr)
&& zone->pathing->NoHazards(End, PathNodes[(*Second)].v))
{
noderoute.erase(First);
@ -652,19 +652,19 @@ void PathManager::SimpleMeshTest()
fflush(stdout);
}
VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChanged, bool &NodeReached)
Map::Vertex Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChanged, bool &NodeReached)
{
WaypointChanged = false;
NodeReached = false;
VERTEX NodeLoc;
Map::Vertex NodeLoc;
VERTEX From(GetX(), GetY(), GetZ());
Map::Vertex From(GetX(), GetY(), GetZ());
VERTEX HeadPosition(From.x, From.y, From.z + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION);
Map::Vertex HeadPosition(From.x, From.y, From.z + (GetSize() < 6.0 ? 6 : GetSize()) * HEAD_POSITION);
VERTEX To(ToX, ToY, ToZ);
Map::Vertex To(ToX, ToY, ToZ);
bool SameDestination = (To == PathingDestination);
@ -761,7 +761,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo
if((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort))
&& (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold)))
{
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr))
PathingLOSState = HaveLOS;
else
PathingLOSState = NoLOS;
@ -854,7 +854,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo
if((Distance <= RuleR(Pathing, MinDistanceForLOSCheckShort))
&& (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold)))
{
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr))
PathingLOSState = HaveLOS;
else
PathingLOSState = NoLOS;
@ -893,7 +893,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo
&& (ABS(From.z - To.z) <= RuleR(Pathing, ZDiffThreshold)))
{
mlog(PATHING__DEBUG, " Checking for short LOS at distance %8.3f.", Distance);
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr))
PathingLOSState = HaveLOS;
else
PathingLOSState = NoLOS;
@ -1046,7 +1046,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo
{
mlog(PATHING__DEBUG, " Checking for long LOS at distance %8.3f.", Distance);
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(HeadPosition, To, 1.0f, nullptr))
PathingLOSState = HaveLOS;
else
PathingLOSState = NoLOS;
@ -1090,7 +1090,7 @@ VERTEX Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Waypo
}
int PathManager::FindNearestPathNode(VERTEX Position)
int PathManager::FindNearestPathNode(Map::Vertex Position)
{
// Find the nearest PathNode we have LOS to.
@ -1126,7 +1126,7 @@ int PathManager::FindNearestPathNode(VERTEX Position)
{
_log(PATHING__DEBUG, "Checking Reachability of Node %i from Start Position.", PathNodes[(*Iterator).id].id);
if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr, nullptr))
if(!zone->zonemap->LineIntersectsZone(Position, PathNodes[(*Iterator).id].v, 1.0f, nullptr))
{
ClosestPathNodeToStart = (*Iterator).id;
break;
@ -1140,13 +1140,13 @@ int PathManager::FindNearestPathNode(VERTEX Position)
return ClosestPathNodeToStart;
}
bool PathManager::NoHazards(VERTEX From, VERTEX To)
bool PathManager::NoHazards(Map::Vertex From, Map::Vertex To)
{
// Test the Z coordinate at the mid point.
//
VERTEX MidPoint((From.x + To.x) / 2, (From.y + To.y) / 2, From.z);
Map::Vertex MidPoint((From.x + To.x) / 2, (From.y + To.y) / 2, From.z);
float NewZ = zone->zonemap->FindBestZ(MAP_ROOT_NODE, MidPoint, nullptr, nullptr);
float NewZ = zone->zonemap->FindBestZ(MidPoint, nullptr);
if(ABS(NewZ - From.z) > RuleR(Pathing, ZDiffThreshold))
{
@ -1164,10 +1164,10 @@ bool PathManager::NoHazards(VERTEX From, VERTEX To)
return true;
}
bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To)
bool PathManager::NoHazardsAccurate(Map::Vertex From, Map::Vertex To)
{
float stepx, stepy, stepz, curx, cury, curz;
VERTEX cur = From;
Map::Vertex cur = From;
float last_z = From.z;
float step_size = 1.0;
@ -1181,13 +1181,13 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To)
stepy = (float)To.y - cury;
stepz = (float)To.z - curz;
float factor = sqrt(stepx*stepx + stepy*stepy + stepz*stepz);
stepx = (stepx/factor)*step_size;
stepy = (stepy/factor)*step_size;
stepz = (stepz/factor)*step_size;
stepx = (stepx / factor)*step_size;
stepy = (stepy / factor)*step_size;
stepz = (stepz / factor)*step_size;
VERTEX TestPoint(curx, cury, curz);
float NewZ = zone->zonemap->FindBestZ(MAP_ROOT_NODE, TestPoint, nullptr, nullptr);
if(ABS(NewZ - last_z) > 5.0)
Map::Vertex TestPoint(curx, cury, curz);
float NewZ = zone->zonemap->FindBestZ(TestPoint, nullptr);
if (ABS(NewZ - last_z) > 5.0f)
{
_log(PATHING__DEBUG, " HAZARD DETECTED moving from %8.3f, %8.3f, %8.3f to %8.3f, %8.3f, %8.3f. Best Z %8.3f, Z Change is %8.3f",
From.x, From.y, From.z, TestPoint.x, TestPoint.y, TestPoint.z, NewZ, NewZ - From.z);
@ -1195,49 +1195,45 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To)
}
last_z = NewZ;
if(zone->watermap)
if (zone->watermap)
{
NodeRef n = zone->zonemap->SeekNode( zone->zonemap->GetRoot(), TestPoint.x, TestPoint.y);
if(n != NODE_NONE)
if (zone->watermap->InLiquid(From.x, From.y, From.z) || zone->watermap->InLiquid(To.x, To.y, To.z))
{
if(zone->watermap->InLiquid(From.x, From.y, From.z) || zone->watermap->InLiquid(To.x, To.y, To.z))
{
break;
}
break;
}
if(zone->watermap->InLiquid(TestPoint.x, TestPoint.y, NewZ))
if (zone->watermap->InLiquid(TestPoint.x, TestPoint.y, NewZ))
{
Map::Vertex TestPointWater(TestPoint.x, TestPoint.y, NewZ - 0.5f);
Map::Vertex TestPointWaterDest = TestPointWater;
Map::Vertex hit;
TestPointWaterDest.z -= 500;
float best_z2 = -999990;
if (zone->zonemap->LineIntersectsZone(TestPointWater, TestPointWaterDest, 1.0f, &hit))
{
VERTEX TestPointWater(TestPoint.x, TestPoint.y, NewZ-0.5);
VERTEX TestPointWaterDest(TestPointWater);
VERTEX hit;
TestPointWaterDest.z -= 500;
float best_z2 = -999990;
if(zone->zonemap->LineIntersectsNode(n, TestPointWater, TestPointWaterDest, &hit, nullptr))
best_z2 = hit.z;
}
if (best_z2 == -999990)
{
_log(PATHING__DEBUG, " HAZARD DETECTED, really deep water/lava!");
return false;
}
else
{
if (ABS(NewZ - best_z2) > RuleR(Pathing, ZDiffThreshold))
{
best_z2 = hit.z;
}
if(best_z2 == -999990)
{
_log(PATHING__DEBUG, " HAZARD DETECTED, really deep water/lava!");
_log(PATHING__DEBUG, " HAZARD DETECTED, water is fairly deep at %8.3f units deep", ABS(NewZ - best_z2));
return false;
}
else
{
if(ABS(NewZ - best_z2) > RuleR(Pathing, ZDiffThreshold))
{
_log(PATHING__DEBUG, " HAZARD DETECTED, water is fairly deep at %8.3f units deep", ABS(NewZ - best_z2));
return false;
}
else
{
_log(PATHING__DEBUG, " HAZARD NOT DETECTED, water is shallow at %8.3f units deep", ABS(NewZ - best_z2));
}
_log(PATHING__DEBUG, " HAZARD NOT DETECTED, water is shallow at %8.3f units deep", ABS(NewZ - best_z2));
}
}
else
{
_log(PATHING__DEBUG, "Hazard point not in water or lava!");
}
}
else
{
_log(PATHING__DEBUG, "Hazard point not in water or lava!");
}
}
else
@ -1253,12 +1249,11 @@ bool PathManager::NoHazardsAccurate(VERTEX From, VERTEX To)
cur.y = cury;
cur.z = curz;
if(ABS(curx - To.x) < step_size) cur.x = To.x;
if(ABS(cury - To.y) < step_size) cur.y = To.y;
if(ABS(curz - To.z) < step_size) cur.z = To.z;
if (ABS(curx - To.x) < step_size) cur.x = To.x;
if (ABS(cury - To.y) < step_size) cur.y = To.y;
if (ABS(curz - To.z) < step_size) cur.z = To.z;
}
while(cur.x != To.x || cur.y != To.y || cur.z != To.z);
} while (cur.x != To.x || cur.y != To.y || cur.z != To.z);
return true;
}
@ -2001,8 +1996,8 @@ void PathManager::MoveNode(Client *c)
if(zone->zonemap)
{
VERTEX loc(c->GetX(), c->GetY(), c->GetZ());
Node->bestz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, loc, nullptr, nullptr);
Map::Vertex loc(c->GetX(), c->GetY(), c->GetZ());
Node->bestz = zone->zonemap->FindBestZ(loc, nullptr);
}
else
{
@ -2072,14 +2067,14 @@ bool PathManager::NodesConnected(PathNode *a, PathNode *b)
return false;
}
bool PathManager::CheckLosFN(VERTEX a, VERTEX b)
bool PathManager::CheckLosFN(Map::Vertex a, Map::Vertex b)
{
if(zone->zonemap)
{
VERTEX hit;
Map::Vertex hit;
VERTEX myloc;
VERTEX oloc;
Map::Vertex myloc;
Map::Vertex oloc;
myloc.x = a.x;
myloc.y = a.y;
@ -2090,7 +2085,7 @@ bool PathManager::CheckLosFN(VERTEX a, VERTEX b)
oloc.z = b.z;
if(zone->zonemap->LineIntersectsZone(myloc, oloc, 1.0f, nullptr, nullptr))
if(zone->zonemap->LineIntersectsZone(myloc, oloc, 1.0f, nullptr))
{
return false;
}

View File

@ -31,7 +31,7 @@ struct NeighbourNode {
struct PathNode {
uint16 id;
VERTEX v;
Map::Vertex v;
float bestz;
NeighbourNode Neighbours[PATHNODENEIGHBOURS];
};
@ -61,17 +61,17 @@ public:
static PathManager *LoadPathFile(const char *ZoneName);
bool loadPaths(FILE *fp);
void PrintPathing();
std::list<int> FindRoute(VERTEX Start, VERTEX End);
std::list<int> FindRoute(Map::Vertex Start, Map::Vertex End);
std::list<int> FindRoute(int startID, int endID);
VERTEX GetPathNodeCoordinates(int NodeNumber, bool BestZ = true);
bool CheckLosFN(VERTEX a, VERTEX b);
Map::Vertex GetPathNodeCoordinates(int NodeNumber, bool BestZ = true);
bool CheckLosFN(Map::Vertex a, Map::Vertex b);
void SpawnPathNodes();
void MeshTest();
void SimpleMeshTest();
int FindNearestPathNode(VERTEX Position);
bool NoHazards(VERTEX From, VERTEX To);
bool NoHazardsAccurate(VERTEX From, VERTEX To);
int FindNearestPathNode(Map::Vertex Position);
bool NoHazards(Map::Vertex From, Map::Vertex To);
bool NoHazardsAccurate(Map::Vertex From, Map::Vertex To);
void OpenDoors(int Node1, int Node2, Mob* ForWho);
PathNode* FindPathNodeByCoordinates(float x, float y, float z);

View File

@ -5898,6 +5898,64 @@ XS(XS_Client_PlayMP3)
XSRETURN_EMPTY;
}
XS(XS_Client_ExpeditionMessage); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_ExpeditionMessage)
{
dXSARGS;
if (items != 3)
Perl_croak(aTHX_ "Usage: Client::ExpeditionMessage(THIS, ExpdID, Message)");
{
Client * THIS;
int ExpdID = (int)SvUV(ST(1));
const char * Message = (const char *)SvPV_nolen(ST(2));
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.");
THIS->ExpeditionSay(Message, ExpdID);
}
XSRETURN_EMPTY;
}
//Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg)
XS(XS_Client_SendMarqueeMessage); /* prototype to pass -Wmissing-prototypes */
XS(XS_Client_SendMarqueeMessage)
{
dXSARGS;
if (items != 7)
Perl_croak(aTHX_ "Usage: Client::SendMarqueeMessage(THIS, type, priority, fade_in, fade_out, duration, msg)");
{
Client * THIS;
uint32 type = (uint32)SvUV(ST(1));
uint32 priority = (uint32)SvUV(ST(2));
uint32 fade_in = (uint32)SvUV(ST(3));
uint32 fade_out = (uint32)SvUV(ST(4));
uint32 duration = (uint32)SvUV(ST(5));
std::string msg = (std::string)SvPV_nolen(ST(6));
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.");
THIS->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, msg);
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus
extern "C"
#endif
@ -6134,6 +6192,8 @@ XS(boot_Client)
newXSproto(strcpy(buf, "SilentMessage"), XS_Client_SilentMessage, file, "$$");
newXSproto(strcpy(buf, "PlayMP3"), XS_Client_PlayMP3, file, "$;$");
newXSproto(strcpy(buf, "SendTargetCommand"), XS_Client_SendTargetCommand, file, "$$");
newXSproto(strcpy(buf, "ExpeditionMessage"), XS_Client_ExpeditionMessage, file, "$$$");
newXSproto(strcpy(buf, "SendMarqueeMessage"), XS_Client_SendMarqueeMessage, file, "$$$$$$$");
XSRETURN_YES;
}

View File

@ -6856,7 +6856,7 @@ XS(XS_Mob_ProjectileAnim); /* prototype to pass -Wmissing-prototypes */
XS(XS_Mob_ProjectileAnim)
{
dXSARGS;
if (items < 3 || items > 8)
if (items < 3 || items > 9)
Perl_croak(aTHX_ "Usage: Mob::ProjectileAnim(THIS, mob, item_id, IsArrow?, speed, angle, tilt, arc)");
{
@ -6868,6 +6868,7 @@ XS(XS_Mob_ProjectileAnim)
float angle = 0;
float tilt = 0;
float arc = 0;
char * IDFile = nullptr;
if (sv_derived_from(ST(0), "Mob")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
@ -6903,7 +6904,9 @@ XS(XS_Mob_ProjectileAnim)
arc = (float)SvNV(ST(7));
}
THIS->ProjectileAnimation(mob, item_id, IsArrow, speed, angle, tilt, arc);
if (items > 8) { IDFile = (char *)SvPV_nolen(ST(8)); }
THIS->ProjectileAnimation(mob, item_id, IsArrow, speed, angle, tilt, arc, IDFile);
}
XSRETURN_EMPTY;
@ -8389,7 +8392,7 @@ XS(boot_Mob)
newXSproto(strcpy(buf, "CheckLoS"), XS_Mob_CheckLoS, file, "$$");
newXSproto(strcpy(buf, "CheckLoSToLoc"), XS_Mob_CheckLoSToLoc, file, "$$$$;$");
newXSproto(strcpy(buf, "FindGroundZ"), XS_Mob_FindGroundZ, file, "$$$;$");
newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$");
newXSproto(strcpy(buf, "ProjectileAnim"), XS_Mob_ProjectileAnim, file, "$$$;$$$$$$");
newXSproto(strcpy(buf, "HasNPCSpecialAtk"), XS_Mob_HasNPCSpecialAtk, file, "$$");
newXSproto(strcpy(buf, "SendAppearanceEffect"), XS_Mob_SendAppearanceEffect, file, "$$;$$$$");
newXSproto(strcpy(buf, "SetFlyMode"), XS_Mob_SetFlyMode, file, "$$");

View File

@ -2023,7 +2023,7 @@ XS(XS_NPC_GetSlowMitigation)
Perl_croak(aTHX_ "Usage: NPC::GetSlowMitigation(THIS)");
{
NPC * THIS;
float RETVAL;
int16 RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
@ -2036,7 +2036,7 @@ XS(XS_NPC_GetSlowMitigation)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
RETVAL = THIS->GetSlowMitigation();
XSprePUSH; PUSHn((double)RETVAL);
XSprePUSH; PUSHn((UV)RETVAL);
}
XSRETURN(1);
}

View File

@ -1277,16 +1277,9 @@ void QuestManager::signalwith(int npc_id, int signal_id, int wait_ms) {
if(wait_ms > 0) {
STimerList.push_back(SignalTimer(wait_ms, npc_id, signal_id));
return;
}
if (npc_id<1)
{
printf("signal() bad npcid=%i\n",npc_id);
}
else
{
//initiator* signalnpc=0;
entity_list.SignalMobsByNPCID(npc_id, signal_id);
} else {
STimerList.push_back(SignalTimer(0, npc_id, signal_id));
return;
}
}
@ -1740,8 +1733,8 @@ short QuestManager::get_spawn_condition(const char *zone_short, uint32 instance_
}
//toggle a spawn event
void QuestManager::toggle_spawn_event(int event_id, bool enable, bool reset_base) {
zone->spawn_conditions.ToggleEvent(event_id, enable, reset_base);
void QuestManager::toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) {
zone->spawn_conditions.ToggleEvent(event_id, enable, strict, reset_base);
}
bool QuestManager::has_zone_flag(int zone_id) {

View File

@ -150,7 +150,7 @@ public:
void showgrid(int gridid);
void spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id, short new_value);
short get_spawn_condition(const char *zone_short, uint32 instance_id, uint16 condition_id);
void toggle_spawn_event(int event_id, bool enable, bool reset_base);
void toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base);
bool has_zone_flag(int zone_id);
void set_zone_flag(int zone_id);
void clear_zone_flag(int zone_id);

View File

@ -303,11 +303,13 @@ void Spawn2::ForceDespawn()
{
npcthis->Depop(true);
IsDespawned = true;
npcthis = nullptr;
return;
}
else
{
npcthis->Depop(false);
npcthis = nullptr;
}
}
}
@ -555,6 +557,7 @@ SpawnEvent::SpawnEvent() {
action = ActionSet;
argument = 0;
period = 0xFFFFFFFF;
strict = false;
memset(&next, 0, sizeof(next));
}
@ -586,26 +589,28 @@ void SpawnConditionManager::Process() {
for(; cur != end; ++cur) {
SpawnEvent &cevent = *cur;
if(!cevent.enabled)
continue;
if(cevent.enabled)
{
if(EQTime::IsTimeBefore(&tod, &cevent.next)) {
//this event has been triggered.
//execute the event
if(!cevent.strict || (cevent.strict && cevent.next.hour == tod.hour && cevent.next.day == tod.day && cevent.next.month == tod.month && cevent.next.year == tod.year))
ExecEvent(cevent, true);
if(EQTime::IsTimeBefore(&tod, &cevent.next)) {
//this event has been triggered.
//execute the event
ExecEvent(cevent, true);
//add the period of the event to the trigger time
EQTime::AddMinutes(cevent.period, &cevent.next);
std::string t;
EQTime::ToString(&cevent.next, t);
_log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", cevent.id, cevent.period, t.c_str());
//save the next event time in the DB
UpdateDBEvent(cevent);
//find the next closest event timer.
FindNearestEvent();
//minor optimization, if theres no more possible events,
//then stop trying... I dunno how worth while this is.
if(EQTime::IsTimeBefore(&next_event, &tod))
return;
//add the period of the event to the trigger time
EQTime::AddMinutes(cevent.period, &cevent.next);
std::string t;
EQTime::ToString(&cevent.next, t);
_log(SPAWNS__CONDITIONS, "Event %d: Will trigger again in %d EQ minutes at %s.", cevent.id, cevent.period, t.c_str());
//save the next event time in the DB
UpdateDBEvent(cevent);
//find the next closest event timer.
FindNearestEvent();
//minor optimization, if theres no more possible events,
//then stop trying... I dunno how worth while this is.
if(EQTime::IsTimeBefore(&next_event, &tod))
return;
}
}
}
}
@ -619,6 +624,14 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) {
return; //unable to find the spawn condition to operate on
}
TimeOfDay_Struct tod;
zone->zone_time.getEQTimeOfDay(&tod);
if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year))
{
_log(SPAWNS__CONDITIONS, "Event %d: Unable to execute. Condition is strict, and event time has already passed.", event.id);
return;
}
SpawnCondition &cond = condi->second;
int16 new_value = cond.value;
@ -666,10 +679,10 @@ void SpawnConditionManager::UpdateDBEvent(SpawnEvent &event) {
len = MakeAnyLenString(&query,
"UPDATE spawn_events SET "
"next_minute=%d, next_hour=%d, next_day=%d, next_month=%d, "
"next_year=%d, enabled=%d "
"next_year=%d, enabled=%d, strict=%d "
"WHERE id=%d",
event.next.minute, event.next.hour, event.next.day, event.next.month,
event.next.year, event.enabled?1:0, event.id
event.next.year, event.enabled?1:0, event.strict?1:0,event.id
);
if(!database.RunQuery(query, len, errbuf)) {
LogFile->write(EQEMuLog::Error, "Unable to update spawn event '%s': %s\n", query, errbuf);
@ -703,7 +716,7 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std:
bool ret = false;
len = MakeAnyLenString(&query,
"SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,zone "
"SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict,zone "
"FROM spawn_events WHERE id=%d", event_id);
if (database.RunQuery(query, len, errbuf, &result)) {
safe_delete_array(query);
@ -721,12 +734,13 @@ bool SpawnConditionManager::LoadDBEvent(uint32 event_id, SpawnEvent &event, std:
event.enabled = atoi(row[8])==0?false:true;
event.action = (SpawnEvent::Action) atoi(row[9]);
event.argument = atoi(row[10]);
zone_name = row[11];
event.strict = atoi(row[11])==0?false:true;
zone_name = row[12];
std::string t;
EQTime::ToString(&event.next, t);
_log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d. Will trigger at %s",
event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, t.c_str());
_log(SPAWNS__CONDITIONS, "(LoadDBEvent) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d. Will trigger at %s",
event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict, t.c_str());
ret = true;
}
@ -794,7 +808,7 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in
//load spawn events
SpawnEvent event;
len = MakeAnyLenString(&query,
"SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument "
"SELECT id,cond_id,period,next_minute,next_hour,next_day,next_month,next_year,enabled,action,argument,strict "
"FROM spawn_events WHERE zone='%s'", zone_name);
if (database.RunQuery(query, len, errbuf, &result)) {
safe_delete_array(query);
@ -816,10 +830,11 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in
event.enabled = atoi(row[8])==0?false:true;
event.action = (SpawnEvent::Action) atoi(row[9]);
event.argument = atoi(row[10]);
event.strict = atoi(row[11])==0?false:true;
spawn_events.push_back(event);
_log(SPAWNS__CONDITIONS, "Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d",
event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument);
_log(SPAWNS__CONDITIONS, "(LoadSpawnConditions) Loaded %s spawn event %d on condition %d with period %d, action %d, argument %d, strict %d",
event.enabled?"enabled":"disabled", event.id, event.condition_id, event.period, event.action, event.argument, event.strict);
}
mysql_free_result(result);
} else {
@ -847,34 +862,48 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in
for(; cur != end; ++cur) {
SpawnEvent &cevent = *cur;
if(!cevent.enabled)
continue;
bool StrictCheck = false;
if(cevent.strict &&
cevent.next.hour == tod.hour &&
cevent.next.day == tod.day &&
cevent.next.month == tod.month &&
cevent.next.year == tod.year)
StrictCheck = true;
//watch for special case of all 0s, which means to reset next to now
if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) {
_log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id);
memcpy(&cevent.next, &tod, sizeof(cevent.next));
//add one period
EQTime::AddMinutes(cevent.period, &cevent.next);
//save it in the db.
UpdateDBEvent(cevent);
continue; //were done with this event.
}
//If event is disabled, or we failed the strict check, set initial spawn_condition to 0.
if(!cevent.enabled || !StrictCheck)
SetCondition(zone->GetShortName(), zone->GetInstanceID(),cevent.condition_id,0);
ran = false;
while(EQTime::IsTimeBefore(&tod, &cevent.next)) {
_log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id);
//this event has been triggered.
//execute the event
ExecEvent(cevent, false);
//add the period of the event to the trigger time
EQTime::AddMinutes(cevent.period, &cevent.next);
ran = true;
}
//only write it out if the event actually ran
if(ran) {
//save the event in the DB
UpdateDBEvent(cevent);
if(cevent.enabled)
{
//watch for special case of all 0s, which means to reset next to now
if(cevent.next.year == 0 && cevent.next.month == 0 && cevent.next.day == 0 && cevent.next.hour == 0 && cevent.next.minute == 0) {
_log(SPAWNS__CONDITIONS, "Initial next trigger time set for spawn event %d", cevent.id);
memcpy(&cevent.next, &tod, sizeof(cevent.next));
//add one period
EQTime::AddMinutes(cevent.period, &cevent.next);
//save it in the db.
UpdateDBEvent(cevent);
continue; //were done with this event.
}
ran = false;
while(EQTime::IsTimeBefore(&tod, &cevent.next)) {
_log(SPAWNS__CONDITIONS, "Catch up triggering on event %d", cevent.id);
//this event has been triggered.
//execute the event
if(!cevent.strict || StrictCheck)
ExecEvent(cevent, false);
//add the period of the event to the trigger time
EQTime::AddMinutes(cevent.period, &cevent.next);
ran = true;
}
//only write it out if the event actually ran
if(ran) {
//save the event in the DB
UpdateDBEvent(cevent);
}
}
}
@ -894,14 +923,14 @@ void SpawnConditionManager::FindNearestEvent() {
int next_id = -1;
for(; cur != end; ++cur) {
SpawnEvent &cevent = *cur;
if(!cevent.enabled)
continue;
//see if this event is before our last nearest
if(EQTime::IsTimeBefore(&next_event, &cevent.next)) {
memcpy(&next_event, &cevent.next, sizeof(next_event));
next_id = cevent.id;
if(cevent.enabled)
{
//see if this event is before our last nearest
if(EQTime::IsTimeBefore(&next_event, &cevent.next))
{
memcpy(&next_event, &cevent.next, sizeof(next_event));
next_id = cevent.id;
}
}
}
if(next_id == -1)
@ -1035,7 +1064,7 @@ void SpawnConditionManager::ReloadEvent(uint32 event_id) {
FindNearestEvent();
}
void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool reset_base) {
void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base) {
_log(SPAWNS__CONDITIONS, "Request to %s spawn event %d %sresetting trigger time", enabled?"enable":"disable", event_id, reset_base?"":"without ");
@ -1048,8 +1077,9 @@ void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool rese
if(cevent.id == event_id) {
//make sure were actually changing something
if(cevent.enabled != enabled || reset_base) {
if(cevent.enabled != enabled || reset_base || cevent.strict != strict) {
cevent.enabled = enabled;
cevent.strict = strict;
if(reset_base) {
_log(SPAWNS__CONDITIONS, "Spawn event %d located in this zone. State set. Trigger time reset (period %d).", event_id, cevent.period);
//start with the time now

View File

@ -67,6 +67,7 @@ public:
bool NPCPointerValid() { return (npcthis!=nullptr); }
void SetNPCPointer(NPC* n) { npcthis = n; }
void SetNPCPointerNull() { npcthis = nullptr; }
void SetTimer(uint32 duration) { timer.Start(duration); }
uint32 GetKillCount() { return killcount; }
protected:
@ -134,6 +135,7 @@ public:
bool enabled;
Action action;
int16 argument;
bool strict;
uint32 period; //eq minutes (3 seconds) between events
TimeOfDay_Struct next; //next time this event triggers
@ -148,7 +150,7 @@ public:
int16 GetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id);
void SetCondition(const char *zone_short, uint32 instance_id, uint16 condition_id, int16 new_value, bool world_update = false);
void ToggleEvent(uint32 event_id, bool enabled, bool reset_base);
void ToggleEvent(uint32 event_id, bool enabled, bool strict, bool reset_base);
bool Check(uint16 condition, int16 min_value);
void ReloadEvent(uint32 event_id);

View File

@ -839,6 +839,9 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) {
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
@ -1085,6 +1088,9 @@ void NPC::RangedAttack(Mob* other)
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
@ -1227,6 +1233,9 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51
invisible_animals = false;
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
@ -1337,7 +1346,7 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil
safe_delete(outapp);
}
void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc) {
void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile) {
const Item_Struct* item = nullptr;
uint8 item_type = 0;
@ -1371,6 +1380,10 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f
arc = 50;
}
const char *item_IDFile = item->IDFile;
if (IDFile && (strncmp(IDFile, "IT", 2) == 0))
item_IDFile = IDFile;
// See SendItemAnimation() for some notes on this struct
EQApplicationPacket *outapp = new EQApplicationPacket(OP_SomeItemPacketMaybe, sizeof(Arrow_Struct));
@ -1384,7 +1397,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f
as->item_id = item->ID;
as->item_type = item_type;
as->skill = 0; // Doesn't seem to have any effect
strn0cpy(as->model_name, item->IDFile, 16);
strn0cpy(as->model_name, item_IDFile, 16);
as->velocity = speed;
as->launch_angle = angle;
as->tilt = tilt;
@ -1397,7 +1410,6 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f
}
void NPC::DoClassAttacks(Mob *target) {
if(target == nullptr)
return; //gotta have a target for all these

View File

@ -284,20 +284,31 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
#ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Percental Heal: %+i (%d%% max)", spell.max[i], effect_value);
#endif
//im not 100% sure about this implementation.
//the spell value forumula dosent work for these... at least spell 3232 anyways
int32 val = spell.max[i];
int32 val = GetMaxHP() * spell.base[i] / 100;
if(caster)
val = caster->GetActSpellHealing(spell_id, val, this);
//This effect can also do damage by percent.
if (val < 0) {
int32 mhp = GetMaxHP();
int32 cap = mhp * spell.base[i] / 100;
if (-val > spell.max[i])
val = -spell.max[i];
if(cap < val)
val = cap;
if (caster)
val = caster->GetActSpellDamage(spell_id, val, this);
if(val > 0)
}
else
{
if (val > spell.max[i])
val = spell.max[i];
if(caster)
val = caster->GetActSpellHealing(spell_id, val, this);
}
if (val < 0)
Damage(caster, -val, spell_id, spell.skill, false, buffslot, false);
else
HealDamage(val, caster);
break;
@ -326,7 +337,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if(inuse)
break;
Heal();
int32 val = 0;
val = 7500*effect_value;
val = caster->GetActSpellHealing(spell_id, val, this);
if (val > 0)
HealDamage(val, caster);
break;
}
@ -396,10 +413,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
}
case SE_Succor:
{
{
float x, y, z, heading;
const char *target_zone;
x = spell.base[1];
y = spell.base[0];
z = spell.base[2];
@ -426,6 +444,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if(IsClient())
{
if(MakeRandomInt(0, 99) < RuleI(Spells, SuccorFailChance)) { //2% Fail chance by default
if(IsClient()) {
CastToClient()->Message_StringID(MT_SpellFailure,SUCCOR_FAIL);
}
break;
}
// Below are the spellid's for known evac/succor spells that send player
// to the current zone's safe points.
@ -441,10 +467,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
#ifdef SPELL_EFFECT_SPAM
LogFile->write(EQEMuLog::Debug, "Succor/Evacuation Spell In Same Zone.");
#endif
if(IsClient())
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x, y, z, heading, 0, EvacToSafeCoords);
else
GMMove(x, y, z, heading);
if(IsClient())
CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x, y, z, heading, 0, EvacToSafeCoords);
else
GMMove(x, y, z, heading);
}
else {
#ifdef SPELL_EFFECT_SPAM
@ -457,7 +483,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
break;
}
case SE_YetAnotherGate: //Shin: Used on Teleport Bind.
case SE_GateCastersBindpoint: //Shin: Used on Teleport Bind.
case SE_Teleport: // gates, rings, circles, etc
case SE_Teleport2:
{
@ -489,7 +515,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
}
}
if (effect == SE_YetAnotherGate && caster->IsClient())
if (effect == SE_GateCastersBindpoint && caster->IsClient())
{ //Shin: Teleport Bind uses caster's bind point
x = caster->CastToClient()->GetBindX();
y = caster->CastToClient()->GetBindY();
@ -857,7 +883,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
break;
}
case SE_BindAffinity:
case SE_BindAffinity: //TO DO: Add support for secondary and tertiary gate abilities
{
#ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Bind Affinity");
@ -989,13 +1015,18 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
break;
}
case SE_Gate:
case SE_Gate: //TO DO: Add support for secondary and tertiary gate abilities (base2)
{
#ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Gate");
#endif
if(!spellbonuses.AntiGate)
Gate();
if(!spellbonuses.AntiGate){
if(MakeRandomInt(0, 99) < effect_value)
Gate();
else
caster->Message_StringID(MT_SpellFailure,GATE_FAIL);
}
break;
}
@ -1378,7 +1409,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
(
spell.base[i],
Mob::GetDefaultGender(spell.base[i], GetGender()),
spell.base2[i]
spell.base2[i],
spell.max[i]
);
if(spell.base[i] == OGRE){
SendAppearancePacket(AT_Size, 9);
@ -1554,8 +1586,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
if(spell_id == 2488) //Dook- Lifeburn fix
break;
if(IsClient())
CastToClient()->SetFeigned(true);
if(IsClient()) {
if (MakeRandomInt(0, 99) > spells[spell_id].base[i]) {
CastToClient()->SetFeigned(false);
entity_list.MessageClose_StringID(this, false, 200, 10, STRING_FEIGNFAILED, GetName());
}
else
CastToClient()->SetFeigned(true);
}
break;
}
@ -1644,6 +1683,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
{
CastToClient()->SummonHorse(spell_id);
}
break;
}
@ -1691,19 +1732,25 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
// Now we should either be casting this on self or its being cast on a valid group member
if(TargetClient) {
Corpse *corpse = entity_list.GetCorpseByOwner(TargetClient);
if(corpse) {
if(TargetClient == this->CastToClient())
Message_StringID(4, SUMMONING_CORPSE, TargetClient->CastToMob()->GetCleanName());
else
Message_StringID(4, SUMMONING_CORPSE_OTHER, TargetClient->CastToMob()->GetCleanName());
corpse->Summon(CastToClient(), true, true);
}
else {
// No corpse found in the zone
Message_StringID(4, CORPSE_CANT_SENSE);
if (TargetClient->GetLevel() <= effect_value){
Corpse *corpse = entity_list.GetCorpseByOwner(TargetClient);
if(corpse) {
if(TargetClient == this->CastToClient())
Message_StringID(4, SUMMONING_CORPSE, TargetClient->CastToMob()->GetCleanName());
else
Message_StringID(4, SUMMONING_CORPSE_OTHER, TargetClient->CastToMob()->GetCleanName());
corpse->Summon(CastToClient(), true, true);
}
else {
// No corpse found in the zone
Message_StringID(4, CORPSE_CANT_SENSE);
}
}
else
caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ);
}
else {
Message_StringID(4, TARGET_NOT_FOUND);
@ -2590,14 +2637,37 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
HealDamage(dmg, caster);
}
}
break;
}
case SE_Taunt:
{
if (IsNPC())
caster->Taunt(this->CastToNPC(), false, spell.base[i]);
break;
}
case SE_AttackSpeed:
if (spell.base[i] < 100)
SlowMitigation(caster);
break;
case SE_AttackSpeed2:
if (spell.base[i] < 100)
SlowMitigation(caster);
break;
case SE_AttackSpeed3:
if (spell.base[i] < 0)
SlowMitigation(caster);
break;
case SE_AttackSpeed4:
SlowMitigation(caster);
break;
// Handled Elsewhere
case SE_ImmuneFleeing:
case SE_NegateSpellEffect:
@ -2683,10 +2753,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
case SE_DivineSave:
case SE_Accuracy:
case SE_Flurry:
case SE_AttackSpeed:
case SE_AttackSpeed2:
case SE_AttackSpeed3:
case SE_AttackSpeed4:
case SE_ImprovedDamage:
case SE_ImprovedHeal:
case SE_IncreaseSpellHaste:
@ -3317,10 +3383,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
case SE_Root: {
/* Root formula derived from extensive personal live parses - Kayen
ROOT has a 40% chance to do a resist check to break.
Resist check has NO LOWER bounds.
If multiple roots on target. Root in first slot will be checked first to break from nukes.
If multiple roots on target and broken by spell. Roots are removed ONE at a time in order of buff slot.
ROOT has a 70% chance to do a resist check to break.
*/
if (MakeRandomInt(0, 99) < RuleI(Spells, RootBreakCheckChance)){
@ -3936,6 +3999,15 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
bool LimitFailure = false;
bool LimitInclude[MaxLimitInclude] = { false };
/* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells.
0/1 SE_LimitResist
2/3 SE_LimitSpell
4/5 SE_LimitEffect
6/7 SE_LimitTarget
8/9 SE_LimitSpellGroup:
10/11 SE_LimitCastingSkill:
Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes
*/
int FocusCount = 0;
std::map<uint32, std::map<uint32, AA_Ability> >::const_iterator find_iter = aa_effects.find(aa_ID);
@ -3950,7 +4022,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
base1 = iter->second.base1;
base2 = iter->second.base2;
slot = iter->second.slot;
/*
AA Foci's can contain multiple focus effects within the same AA.
To handle this we will not automatically return zero if a limit is found.
@ -4004,8 +4076,11 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
break;
case SE_LimitInstant:
if(spell.buffduration)
if(base1 == 1 && spell.buffduration) //Fail if not instant
LimitFailure = true;
if(base1 == 0 && (spell.buffduration == 0)) //Fail if instant
LimitFailure = true;
break;
case SE_LimitMaxLevel:
@ -4053,13 +4128,14 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
case SE_LimitMinDur:
if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration))
LimitFailure = true;
break;
break;
case SE_LimitEffect:
if(base1 < 0){
if(IsEffectInSpell(spell_id,-base1)) //Exclude
LimitFailure = true;
}
}
else{
LimitInclude[4] = true;
if(IsEffectInSpell(spell_id,base1)) //Include
@ -4104,10 +4180,11 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
break;
case SE_LimitCombatSkills:
if (base1 == 0 && IsCombatSkill(spell_id)) //Exclude Discs
if (base1 == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) //Exclude Discs / Procs
LimitFailure = true;
else if (base1 == 1 && !IsCombatSkill(spell_id)) //Exclude Spells
else if (base1 == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) //Exclude Spells
LimitFailure = true;
break;
case SE_LimitSpellGroup:
@ -4138,7 +4215,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id)
//Do not use this limit more then once per spell. If multiple class, treat value like items would.
if (!PassLimitClass(base1, GetClass()))
LimitFailure = true;
break;
break;
case SE_LimitRace:
if (base1 != GetRace())
@ -4399,8 +4476,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_LimitInstant:
if(spell.buffduration)
if(focus_spell.base[i] == 1 && spell.buffduration) //Fail if not instant
return 0;
if(focus_spell.base[i] == 0 && (spell.buffduration == 0)) //Fail if instant
return 0;
break;
case SE_LimitMaxLevel:
@ -4505,10 +4585,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo
break;
case SE_LimitCombatSkills:
if (focus_spell.base[i] == 0 && IsCombatSkill(spell_id)) //Exclude Disc
return 0;
else if (focus_spell.base[i] == 1 && !IsCombatSkill(spell_id)) //Include Spells
if (focus_spell.base[i] == 0 && (IsCombatSkill(spell_id) || IsCombatProc(spell_id))) //Exclude Discs / Procs
return 0;
else if (focus_spell.base[i] == 1 && (!IsCombatSkill(spell_id) || !IsCombatProc(spell_id))) //Exclude Spells
return 0;
break;
case SE_LimitSpellGroup:
@ -5594,7 +5675,7 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id)
if(!IsValidSpell(spell_id))
return 0;
if (!aabonuses.SEResist[0] && !spellbonuses.SEResist[0] && !itembonuses.SEResist[0])
if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1])
return 0;
uint16 resist_chance = 0;
@ -5986,3 +6067,82 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama
return false;
}
bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){
/*For mage 'Bolt' line and other various spells.
-This is mostly accurate for how the modern clients handle this effect.
-It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic)
-The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+
-There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier
and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target.
-If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles).
-The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly
and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement).
When bolt hits its predicted point the damage is then done to target.
Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant.
Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units.
Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check
because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play)
*/
if (!spell_target)
return false;
uint8 anim = spells[spell_id].CastingAnim;
int bolt_id = -1;
//Make sure there is an avialable bolt to be cast.
for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) {
if (projectile_spell_id[i] == 0){
bolt_id = i;
break;
}
}
if (bolt_id < 0)
return false;
if (CheckLosFN(spell_target)) {
projectile_spell_id[bolt_id] = spell_id;
projectile_target_id[bolt_id] = spell_target->GetID();
projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ();
projectile_increment[bolt_id] = 1;
projectile_timer.Start(250);
}
//This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files.
if (RuleB(Spells, UseLiveSpellProjectileGFX)) {
ProjectileAnimation(spell_target,0, false, 1.5,0,0,0, spells[spell_id].player_1);
}
//This allows limited support for server using older spell files that do not contain data for bolt graphics.
else {
//Only use fire graphic for fire spells.
if (spells[spell_id].resisttype == RESIST_FIRE) {
if (IsClient()){
if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic.
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5);
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5);
}
else
ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5);
}
//Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results)
else
ProjectileAnimation(spell_target,0, 1, 1.5);
}
if (spells[spell_id].CastingAnim == 64)
anim = 44; //Corrects for animation error.
DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation.
return true;
}

View File

@ -211,6 +211,9 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot,
return(false);
}
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(IsClient() && GetTarget() && IsHarmonySpell(spell_id))
{
for(int i = 0; i < EFFECT_COUNT; i++) {
@ -554,6 +557,15 @@ uint16 Mob::GetSpecializeSkillValue(uint16 spell_id) const {
}
void Client::CheckSpecializeIncrease(uint16 spell_id) {
// These are not active because CheckIncreaseSkill() already does so.
// It's such a rare occurance that adding them here is wasted..(ref only)
/*
if (IsDead() || IsUnconscious())
return;
if (IsAIControlled())
return;
*/
switch(spells[spell_id].skill) {
case SkillAbjuration:
CheckIncreaseSkill(SkillSpecializeAbjure, nullptr);
@ -577,6 +589,15 @@ void Client::CheckSpecializeIncrease(uint16 spell_id) {
}
void Client::CheckSongSkillIncrease(uint16 spell_id){
// These are not active because CheckIncreaseSkill() already does so.
// It's such a rare occurance that adding them here is wasted..(ref only)
/*
if (IsDead() || IsUnconscious())
return;
if (IsAIControlled())
return;
*/
switch(spells[spell_id].skill)
{
case SkillSinging:
@ -1809,7 +1830,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
}
// check line of sight to target if it's a detrimental spell
if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id))
if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].targettype != ST_TargetOptional)
{
mlog(SPELLS__CASTING, "Spell %d: cannot see target %s", spell_target->GetName());
Message_StringID(13,CANT_SEE_TARGET);
@ -1874,7 +1895,12 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
if (isproc) {
SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true);
} else {
if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) {
if (spells[spell_id].targettype == ST_TargetOptional){
if (!TrySpellProjectile(spell_target, spell_id))
return false;
}
else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) {
if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) {
// Prevent mana usage/timers being set for beneficial buffs
if(casting_spell_type == 1)
@ -1883,6 +1909,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16
}
}
}
if(IsPlayerIllusionSpell(spell_id)
&& IsClient()
&& CastToClient()->CheckAAEffect(aaEffectProjectIllusion)){
@ -2586,6 +2613,14 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
{
effect1 = sp1.effectid[i];
effect2 = sp2.effectid[i];
if (spellbonuses.Screech == 1) {
if (effect2 == SE_Screech && sp2.base[i] == -1) {
Message_StringID(MT_SpellFailure, SCREECH_BUFF_BLOCK, sp2.name);
return -1;
}
}
if(effect2 == SE_StackingCommand_Overwrite)
{
overwrite_effect = sp2.base[i];
@ -2630,7 +2665,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2,
mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d, but we do not have that effect on that slot. Ignored.",
sp1.name, spellid1, blocked_effect, blocked_slot, blocked_below_value);
}
}
}
}
} else {
mlog(SPELLS__STACKING, "%s (%d) and %s (%d) appear to be in the same line, skipping Stacking Overwrite/Blocking checks",
@ -3419,8 +3454,15 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r
if(spell_effectiveness == 0 || !IsPartialCapableSpell(spell_id) )
{
mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName());
Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
if (spells[spell_id].resisttype == RESIST_PHYSICAL){
Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
}
else {
Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name);
spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name);
}
if(spelltar->IsAIControlled()){
int32 aggro = CheckAggroAmount(spell_id);
@ -4169,67 +4211,83 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
break;
case RESIST_PHYSICAL:
{
if (IsNPC())
target_resist = GetPhR();
else
target_resist = 0;
}
default:
//This is guessed but the others are right
target_resist = (GetSTA() / 4);
target_resist = 0;
}
//Setup our base resist chance.
int resist_chance = 0;
int level_mod = 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)
//Physical Resists are calclated using their own formula derived from extensive parsing.
if (resist_type == RESIST_PHYSICAL) {
level_mod = ResistPhysical(temp_level_diff, caster->GetLevel());
}
else {
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff))
{
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
temp_level_diff = 15;
}
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;
}
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)
int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel();
if(a > 0)
{
level_diff = 0;
temp_level_diff = a;
}
else
{
temp_level_diff = 0;
}
}
else
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
{
level_diff = GetLevel() - caster->GetLevel();
temp_level_diff = 15;
}
if(IsNPC() && temp_level_diff < -9)
{
temp_level_diff = -9;
}
level_mod = temp_level_diff * temp_level_diff / 2;
if(temp_level_diff < 0)
{
level_mod = -level_mod;
}
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);
}
level_mod += (2 * level_diff);
}
if (CharismaCheck)
@ -4376,6 +4434,43 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
}
int Mob::ResistPhysical(int level_diff, uint8 caster_level)
{
/* Physical resists use the standard level mod calculation in
conjunction with a resist fall off formula that greatly prevents you
from landing abilities on mobs that are higher level than you.
After level 12, every 4 levels gained the max level you can hit
your target without a sharp resist penalty is raised by 1.
Extensive parsing confirms this, along with baseline phyiscal resist rates used.
*/
if (level_diff == 0)
return level_diff;
int level_mod = 0;
if (level_diff > 0) {
int ResistFallOff = 0;
if (caster_level <= 12)
ResistFallOff = 3;
else
ResistFallOff = caster_level/4;
if (level_diff > ResistFallOff || level_diff >= 15)
level_mod = ((level_diff * 10) + level_diff)*2;
else
level_mod = level_diff * level_diff / 2;
}
else
level_mod = -(level_diff * level_diff / 2);
return level_mod;
}
int16 Mob::CalcResistChanceBonus()
{
int resistchance = spellbonuses.ResistSpellChance + itembonuses.ResistSpellChance;
@ -4871,6 +4966,28 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) {
return false;
}
bool Mob::IsCombatProc(uint16 spell_id) {
if (RuleB(Spells, FocusCombatProcs))
return false;
if(spell_id == SPELL_UNKNOWN)
return(false);
if ((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0))
{
for (int i = 0; i < MAX_PROCS; i++){
if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id
|| SkillProcs[i].spellID == spell_id || RangedProcs[i].spellID == spell_id){
return true;
}
}
}
return false;
}
bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id) {
if(spell_id == SPELL_UNKNOWN)
return(false);

View File

@ -1972,14 +1972,6 @@ void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation* Task, int T
Task->Activity[ActivityID].GoalCount,
ActivityID);
if(Task->Activity[ActivityID].GoalMethod != METHODQUEST)
{
char buf[24];
snprintf(buf, 23, "%d %d", ActiveTasks[TaskIndex].TaskID, ActiveTasks[TaskIndex].Activity[ActivityID].ActivityID);
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, c, buf, 0);
}
// Flag the activity as complete
ActiveTasks[TaskIndex].Activity[ActivityID].State = ActivityCompleted;
// Unlock subsequent activities for this task
@ -1991,6 +1983,15 @@ void ClientTaskState::IncrementDoneCount(Client *c, TaskInformation* Task, int T
taskmanager->SendSingleActiveTaskToClient(c, TaskIndex, TaskComplete, false);
// Inform the client the task has been updated, both by a chat message
c->Message(0, "Your task '%s' has been updated.", Task->Title);
if(Task->Activity[ActivityID].GoalMethod != METHODQUEST)
{
char buf[24];
snprintf(buf, 23, "%d %d", ActiveTasks[TaskIndex].TaskID, ActiveTasks[TaskIndex].Activity[ActivityID].ActivityID);
buf[23] = '\0';
parse->EventPlayer(EVENT_TASK_STAGE_COMPLETE, c, buf, 0);
}
// If this task is now complete, the Completed tasks will have been
// updated in UnlockActivities. Send the completed task list to the
// client. This is the same sequence the packets are sent on live.

View File

@ -235,9 +235,9 @@ void NPC::UpdateWaypoint(int wp_index)
if(!RuleB(Watermap, CheckForWaterAtWaypoints) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(cur_wp_x, cur_wp_y, cur_wp_z)))
{
VERTEX dest(cur_wp_x, cur_wp_y, cur_wp_z);
Map::Vertex dest(cur_wp_x, cur_wp_y, cur_wp_z);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
if( (newz > -2000) && ABS(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaWaypoint))
cur_wp_z = newz + 1;
@ -565,9 +565,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b
if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos)))
{
VERTEX dest(x_pos, y_pos, z_pos);
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -694,9 +694,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b
if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos)))
{
VERTEX dest(x_pos, y_pos, z_pos);
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -819,9 +819,9 @@ bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool chec
if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos)))
{
VERTEX dest(x_pos, y_pos, z_pos);
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -927,9 +927,9 @@ void NPC::AssignWaypoints(int32 grid) {
if(!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(newwp.x, newwp.y, newwp.z)))
{
VERTEX dest(newwp.x, newwp.y, newwp.z);
Map::Vertex dest(newwp.x, newwp.y, newwp.z);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
if( (newz > -2000) && ABS(newz-dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading))
newwp.z = newz + 1;
@ -982,9 +982,9 @@ void Mob::SendTo(float new_x, float new_y, float new_z) {
if(!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos)))
{
VERTEX dest(x_pos, y_pos, z_pos);
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);
@ -1013,9 +1013,9 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
if(!RuleB(Watermap, CheckForWaterOnSendTo) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(x_pos, y_pos, z_pos)))
{
VERTEX dest(x_pos, y_pos, z_pos);
Map::Vertex dest(x_pos, y_pos, z_pos);
float newz = zone->zonemap->FindBestZ(MAP_ROOT_NODE, dest, nullptr, nullptr);
float newz = zone->zonemap->FindBestZ(dest, nullptr);
mlog(AI__WAYPOINTS, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,x_pos,y_pos,z_pos);

View File

@ -103,7 +103,7 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) {
worldserver.SetZone(0);
return false;
}
zone->zonemap = Map::LoadMapfile(zone->map_name);
zone->zonemap = Map::LoadMapFile(zone->map_name);
zone->watermap = WaterMap::LoadWaterMapfile(zone->map_name);
zone->pathing = PathManager::LoadPathFile(zone->map_name);

View File

@ -33,8 +33,11 @@ ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passw
void ZoneDatabase::ZDBInitVars() {
memset(door_isopen_array, 0, sizeof(door_isopen_array));
npc_spells_maxid = 0;
npc_spellseffects_maxid = 0;
npc_spells_cache = 0;
npc_spellseffects_cache = 0;
npc_spells_loadtried = 0;
npc_spellseffects_loadtried = 0;
max_faction = 0;
faction_array = nullptr;
}
@ -49,6 +52,14 @@ ZoneDatabase::~ZoneDatabase() {
}
safe_delete_array(npc_spells_loadtried);
if (npc_spellseffects_cache) {
for (x=0; x<=npc_spellseffects_maxid; x++) {
safe_delete_array(npc_spellseffects_cache[x]);
}
safe_delete_array(npc_spellseffects_cache);
}
safe_delete_array(npc_spellseffects_loadtried);
if (faction_array != nullptr) {
for (x=0; x <= max_faction; x++) {
if (faction_array[x] != 0)
@ -1047,11 +1058,13 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
"npc_types.FR,"
"npc_types.PR,"
"npc_types.Corrup,"
"npc_types.PhR,"
"npc_types.mindmg,"
"npc_types.maxdmg,"
"npc_types.attack_count,"
"npc_types.special_abilities,"
"npc_types.npc_spells_id,"
"npc_types.npc_spells_effects_id,"
"npc_types.d_meele_texture1,"
"npc_types.d_meele_texture2,"
"npc_types.prim_melee_type,"
@ -1098,7 +1111,8 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
"npc_types.underwater,"
"npc_types.emoteid,"
"npc_types.spellscale,"
"npc_types.healscale";
"npc_types.healscale,"
"npc_types.no_target_hotkey";
MakeAnyLenString(&query, "%s FROM npc_types WHERE id=%d", basic_query, id);
@ -1143,11 +1157,13 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
tmpNPCType->FR = atoi(row[r++]);
tmpNPCType->PR = atoi(row[r++]);
tmpNPCType->Corrup = atoi(row[r++]);
tmpNPCType->PhR = atoi(row[r++]);
tmpNPCType->min_dmg = atoi(row[r++]);
tmpNPCType->max_dmg = atoi(row[r++]);
tmpNPCType->attack_count = atoi(row[r++]);
strn0cpy(tmpNPCType->special_abilities, row[r++], 512);
tmpNPCType->npc_spells_id = atoi(row[r++]);
tmpNPCType->npc_spells_effects_id = atoi(row[r++]);
tmpNPCType->d_meele_texture1 = atoi(row[r++]);
tmpNPCType->d_meele_texture2 = atoi(row[r++]);
tmpNPCType->prim_melee_type = atoi(row[r++]);
@ -1189,7 +1205,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF) << 8;
tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF);
tmpNPCType->armor_tint[0] |= (tmpNPCType->armor_tint[0]) ? (0xFF << 24) : 0;
int i;
if (armor_tint_id > 0)
{
@ -1270,7 +1286,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
tmpNPCType->see_improved_hide = atoi(row[r++])==0?false:true;
tmpNPCType->ATK = atoi(row[r++]);
tmpNPCType->accuracy_rating = atoi(row[r++]);
tmpNPCType->slow_mitigation = atof(row[r++]);
tmpNPCType->slow_mitigation = atoi(row[r++]);
tmpNPCType->maxlevel = atoi(row[r++]);
tmpNPCType->scalerate = atoi(row[r++]);
tmpNPCType->private_corpse = atoi(row[r++]) == 1 ? true : false;
@ -1279,7 +1295,8 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) {
tmpNPCType->emoteid = atoi(row[r++]);
tmpNPCType->spellscale = atoi(row[r++]);
tmpNPCType->healscale = atoi(row[r++]);
tmpNPCType->no_target_hotkey = atoi(row[r++]) == 1 ? true : false;
// If NPC with duplicate NPC id already in table,
// free item we attempted to add.
if (zone->npctable.find(tmpNPCType->npc_id) != zone->npctable.end())

View File

@ -30,6 +30,17 @@ struct DBnpcspells_entries_Struct {
};
#pragma pack()
#pragma pack(1)
struct DBnpcspellseffects_entries_Struct {
int16 spelleffectid;
uint8 minlevel;
uint8 maxlevel;
int32 base;
int32 limit;
int32 max;
};
#pragma pack()
struct DBnpcspells_Struct {
uint32 parent_list;
int16 attack_proc;
@ -38,6 +49,12 @@ struct DBnpcspells_Struct {
DBnpcspells_entries_Struct entries[0];
};
struct DBnpcspellseffects_Struct {
uint32 parent_list;
uint32 numentries;
DBnpcspellseffects_entries_Struct entries[0];
};
struct DBTradeskillRecipe_Struct {
SkillUseTypes tradeskill;
int16 skill_needed;
@ -345,7 +362,9 @@ public:
void AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat);
void AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop);
uint32 GetMaxNPCSpellsID();
uint32 GetMaxNPCSpellsEffectsID();
DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID);
DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID);
/*
* Mercs
@ -475,8 +494,11 @@ protected:
uint32 max_faction;
Faction** faction_array;
uint32 npc_spells_maxid;
uint32 npc_spellseffects_maxid;
DBnpcspells_Struct** npc_spells_cache;
bool* npc_spells_loadtried;
DBnpcspellseffects_Struct** npc_spellseffects_cache;
bool* npc_spellseffects_loadtried;
uint8 door_isopen_array[255];
};

View File

@ -53,6 +53,7 @@ struct NPCType
uint8 helmtexture;
uint32 loottable_id;
uint32 npc_spells_id;
uint32 npc_spells_effects_id;
int32 npc_faction_id;
uint32 merchanttype;
uint32 alt_currency_type;
@ -75,6 +76,7 @@ struct NPCType
int16 PR;
int16 DR;
int16 Corrup;
int16 PhR;
uint8 haircolor;
uint8 beardcolor;
uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye?
@ -110,7 +112,7 @@ struct NPCType
int accuracy_rating; //10 = 1% accuracy
bool findable; //can be found with find command
bool trackable;
float slow_mitigation; // Slow mitigation % in decimal form.
int16 slow_mitigation;
uint8 maxlevel;
uint32 scalerate;
bool private_corpse;
@ -119,6 +121,7 @@ struct NPCType
uint32 emoteid;
float spellscale;
float healscale;
bool no_target_hotkey;
};
/*