Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Development

Development::Development Forum for development topics and for those interested in EQEMu development. (Not a support forum)

Reply
 
Thread Tools Display Modes
  #1  
Old 01-19-2008, 02:47 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default Full function-hooks for rules (implemented)

Full function-hooks for rules (implemented)
Hello guys,

I have been fiddling with EQEmu for a couple weeks, and played EQ for several years. And as many world builders I have embarked on a quest of my own : create a custom server, with my own ideas of what the rules should be.
It obviously means customizing the EQEmu emulator code. Which inevitably leads to code merges hair-pulling down the road as soon as you touch code written by the rest of the community who tries both to implement a sane game mechanic and the pristine EQ rules.
I would like to minimize the code merges I will need to make n the future. As much as I would like to get the bug fixes, improved game mechanics etc, I would also like to keep my code for custom rules like evaluating the fizzle chance, hit probability, damage etc. In a way I would like to benefit from advances in "mechanics", while keeping my own "rules".
Immediately the rules mechanism in EQEmu came to mind. Except they are quite limited, as they only allow for tweaking a few numeric values during the processing. You really need more than that when you develop a custom world (don't you?).

May be then it was my professional background calling to me, but I saw only one solution : allow developers to separate the game/server mechanic from the rules. The usual tool : function hooks.

Let's take an example : the zone server needs to execute the casting of a spell by a player. It means graying its spell gems bar, waiting until the cast time is over, computing the channeling chance if an interrupt can occur, check whether a fizzle happened, then finally cast the spell and apply its effects. If you look at the fizzle computation, the fizzle chance computation is right inside the rest of the spell casting code. Wouldn't it be nice that the casting code simply called a function computing the fizzle chance (a percentage), and that this function could be defined by the rule system ?

I thought it would be nice to have this, so I implemented it. It was not a huge indertaking as most of the rules code was there, it just needed to handle one more case : we had integer, real and boolean rules, now we can have function hooks.

Here is how it works :
- the emulator code chooses to call a function hook at a certain point. This has to be coded in by the emulator developer, as part of the processing. At this point the parameters passed to the hook (character level, skill score etc) are decided, as is the result the emulator code will accept (say, the hook can return a float, which is the fizzle chance)
- the world builder has written a function matching the function signature for the hook, and has compiled it in a shared library (Windows DLL, Unix .so). The library is stored at the same place as the other server executables.
- the world builder has created the appropriate rule in its database, with a value like "library:function", library being the shared library name, function being the hook function the server must call
- the emulator code will call the rules sub-system, who will load the dynamic library and provide the hook(ed) function pointer to the emulator code
- the emulator code calls the function that implements the rule

If the world builder did not define the rule, the default hook is called. It contains the exact code the emulator server currently imbeds in its processing (that is, this code is moved from its current place to a different file in "common", and is the default implementation).

I could successfully test the implementation of this mechanic on Windows and Gentoo Linux. On Windows it relies on the regular LoadLibrary and GetProcAddress APIs. On Linux it uses the functions dlopen and dlsym of library libdl, which should be common on most Linux installations nowadays (I think it originates from FreeBSD, but I could be wrong). For other platforms I have no way to write/test the code so someone would have to take over. The shared library loading itself is pretty much isolated in one "case" branch in rulesys.cpp, so it's only a few lines of code to adapt.

There is one limitation of the implementation, due to how the rules system is implemented : you cannot dynamically save the hook setting to the database. The reason is that the hook is a function pointer, whereas in the database the library name and the function names are stored. This information is not avaiable in the Save function. I can live with that, but someone interested could probably add this functionality ot the rules system.

Last but not least, here is the code to make all this work. I hope it ends up in the official emulator in the end, it would ease my task And, given some time to replace hard-wiring rules into the code by well-thought function hooks, it could make the life of custom world builders easier.

I wanted to add a ZIP file to this post with the CVS DIFF file and the two new files, but it seems I do not ave the permissions. So you will have to suffer the inclusion of the files contents directly in the post ! And as it is too big to fit in one post, you will even get several !

The two files are a header file and an implementation file for the default rules implementation. They go to the "common" directory. Most of the contents are comments.
Reply With Quote
  #2  
Old 01-19-2008, 02:50 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default Cvs Diff part 1

Code:
? output.diff
? Source/common/default_Hooks.cpp
? Source/common/default_Hooks.h
Index: Source/common/rulesys.cpp
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/rulesys.cpp,v
retrieving revision 1.2.2.2
diff -u -b -B -r1.2.2.2 rulesys.cpp
--- Source/common/rulesys.cpp	31 Oct 2006 02:02:54 -0000	1.2.2.2
+++ Source/common/rulesys.cpp	19 Jan 2008 13:37:36 -0000
@@ -20,7 +20,10 @@
 #include "logsys.h"
 #include "database.h"
 #include "MiscFunctions.h"
-
+#ifndef    WIN32
+/* On Linux, include the headers for libdl for dynamic library loading */
+#include <dlfcn.h>
+#endif /* !WIN32 */
 /*
 
  FatherNitwit: Added new rules subsystem to allow game rules to be changed       
@@ -73,7 +76,7 @@
 	"InvalidCategory"
 };
 
-const RuleManager::RuleInfo RuleManager::s_RuleInfo[_IntRuleCount+_RealRuleCount+_BoolRuleCount+1] = {
+const RuleManager::RuleInfo RuleManager::s_RuleInfo[_IntRuleCount+_RealRuleCount+_BoolRuleCount+_HookRuleCount+1] = {
     /* this is done in three steps so we can reliably get to them by index*/
 	#define RULE_INT(cat, rule, default_value) \
 		{ #cat ":" #rule, Category__##cat, IntRule,  Int__##rule  },
@@ -84,6 +87,9 @@
 	#define RULE_BOOL(cat, rule, default_value) \
 		{ #cat ":" #rule, Category__##cat, BoolRule, Bool__##rule },
 	#include "ruletypes.h"
+	#define RULE_HOOK(cat, rule, default_value) \
+		{ #cat ":" #rule, Category__##cat, HookRule, Hook__##rule },
+	#include "ruletypes.h"
 	{ "Invalid Rule", _CatCount, IntRule }
 };
 
@@ -149,6 +155,56 @@
 		m_RuleRealValues[index] = atof(rule_value);
 		_log(RULES__CHANGE, "Set rule %s to value %.13f", rule_name, m_RuleRealValues[index]);
 		break;
+	case HookRule:
+	{	char *fLibraryName = NULL, *fFunctionName = NULL;
+		Hook ThisHook = NULL;
+		const char *Colon = strchr(rule_value, ':');
+		if(Colon == NULL)
+			_log(RULES__ERROR, "Badly-formed hook value '%s' (rule '%s'). It must contain a colon (:). Ignoring this hook", rule_value, rule_name);
+		else
+		{	fLibraryName = (char *) malloc(Colon - rule_value + 128 + 1); /* 128 = let's assume it is more than enough to append a file extension to the library if needed */
+				strncpy(fLibraryName, rule_value, Colon - rule_value); fLibraryName[Colon - rule_value] = '\0';
+		 	fFunctionName = (char *) malloc(strlen(rule_value) - (Colon - rule_value + 1) + 1);
+				strcpy(fFunctionName, Colon + 1);
+		}
+#ifdef    WIN32
+		HMODULE ThisLibrary = NULL;
+		if(fLibraryName != NULL)
+		{	strcat(fLibraryName, ".dll");
+			ThisLibrary = LoadLibrary(fLibraryName);
+			if(ThisLibrary == NULL)
+				_log(RULES__ERROR, "Cannot load library '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fLibraryName, rule_value, rule_name);
+		}
+
+		if(ThisLibrary != NULL)
+		{	ThisHook = GetProcAddress(ThisLibrary, fFunctionName);
+			if(ThisHook == NULL)
+				_log(RULES__ERROR, "Cannot load library function '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fFunctionName, rule_value, rule_name);
+		}
+#else  /* WIN32 */
+		void *ThisLibrary = NULL;
+		if(fLibraryName != NULL)
+		{	strcat(fLibraryName, ".so");
+			ThisLibrary = dlopen(fLibraryName, RTLD_NOW | RTLD_GLOBAL);
+			if(ThisLibrary == NULL)
+				_log(RULES__ERROR, "Cannot load library '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fLibraryName, rule_value, rule_name);
+		}
+
+		if(ThisLibrary != NULL)
+		{	ThisHook = dlsym(ThisLibrary, fFunctionName);
+			if(ThisHook == NULL)
+				_log(RULES__ERROR, "Cannot load library function '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fFunctionName, rule_value, rule_name);
+		}
+		/*_log(RULES__ERROR, "The rule hooks are not yet implemented on this platform. Ignoring hook '%s' for fule '%s'", rule_value, rule_name);*/
+#endif /* WIN32 */
+		if(ThisHook != NULL)
+		{	m_RuleHookValues[index] = ThisHook;
+			_log(RULES__CHANGE, "Set rule %s to value %d", rule_name, (int) m_RuleHookValues[index]);
+		}
+		if(fLibraryName != NULL)	{	free(fLibraryName); fLibraryName = NULL; }
+		if(fFunctionName != NULL)	{	free(fFunctionName); fFunctionName = NULL; }
+		break;
+	}
 	case BoolRule:
 		bool val = false;
 		if(!strcasecmp(rule_value, "on") || !strcasecmp(rule_value, "true") || !strcasecmp(rule_value, "yes") || !strcasecmp(rule_value, "enabled") || !strcmp(rule_value, "1"))
@@ -172,6 +228,8 @@
 		m_RuleRealValues[ Real__##rule ] = default_value;
 	#define RULE_BOOL(cat, rule, default_value) \
 		m_RuleBoolValues[ Bool__##rule ] = default_value;
+	#define RULE_HOOK(cat, rule, default_value) \
+		m_RuleHookValues[ Hook__##rule ] = default_value;
 	#include "ruletypes.h"
 }
 
@@ -202,6 +260,8 @@
 		return(s_RuleInfo[index+_IntRuleCount].name);
 	case BoolRule:
 		return(s_RuleInfo[index+_IntRuleCount+_RealRuleCount].name);
+	case HookRule:
+		return(s_RuleInfo[index+_IntRuleCount+_RealRuleCount+_BoolRuleCount].name);
 	}
 	//should never happen
 	return("InvalidRule??");
@@ -236,6 +296,9 @@
 	for(r = 0; r < _BoolRuleCount; r++) {
 		_SaveRule(db, BoolRule, r);
 	}
+	for(r = 0; r < _HookRuleCount; r++) {
+		_SaveRule(db, HookRule, r);
+	}
 }
 
 
@@ -280,7 +343,10 @@
 void RuleManager::_SaveRule(Database *db, RuleType type, uint16 index) {
 	char vstr[16];
 
-	switch(type) {
+	if(type == HookRule)
+		_log(RULES__ERROR, "Impossible to save hook rule %s in the database. Hook rule saving is not supported", _GetRuleName(type, index));
+	else
+	{	switch(type) {
 	case IntRule:
 		sprintf(vstr, "%d", m_RuleIntValues[index]);
 		break;
@@ -302,6 +368,7 @@
 		_log(RULES__ERROR, "Fauled to set rule in the database: %s: %s", query,errbuf);
 	}
 	safe_delete_array(query);
+	}
 }
 
 
Index: Source/common/rulesys.h
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/rulesys.h,v
retrieving revision 1.2.2.2
diff -u -b -B -r1.2.2.2 rulesys.h
--- Source/common/rulesys.h	31 Oct 2006 02:02:54 -0000	1.2.2.2
+++ Source/common/rulesys.h	19 Jan 2008 10:07:13 -0000
@@ -35,6 +35,8 @@
 	rules->GetRealRule( RuleManager::Real__##rule )
 #define RuleB(cat, rule) \
 	rules->GetBoolRule( RuleManager::Bool__##rule )
+#define RuleH(cat, rule) \
+	rules->GetHookRule( RuleManager::Hook__##rule )
 
 
 #include <vector>
@@ -42,6 +44,7 @@
 #include <map>
 
 #include "types.h"
+#include "../common/default_Hooks.h"
 
 class Database;
 
@@ -70,6 +73,13 @@
 	} BoolType;
 
 	typedef enum {
+	#define RULE_HOOK(cat, rule, default_value) \
+		Hook__##rule,
+	#include "ruletypes.h"
+		_HookRuleCount
+	} HookType;
+
+	typedef enum {
 	#define RULE_CATEGORY(catname) \
 		Category__##catname,
 	#include "ruletypes.h"
@@ -79,9 +89,10 @@
 	static const IntType  InvalidInt  = _IntRuleCount;
 	static const RealType InvalidReal = _RealRuleCount;
 	static const BoolType InvalidBool = _BoolRuleCount;
+	static const HookType InvalidHook = _HookRuleCount;
 	static const CategoryType InvalidCategory = _CatCount;
 	
-	static const uint32 _RulesCount = _IntRuleCount+_RealRuleCount+_BoolRuleCount;
+	static const uint32 _RulesCount = _IntRuleCount+_RealRuleCount+_BoolRuleCount+_HookRuleCount;
 
 	RuleManager();
 	
@@ -89,11 +100,13 @@
 	inline int   GetIntRule (IntType  t) const { return(m_RuleIntValues[t] ); }
 	inline float GetRealRule(RealType t) const { return(m_RuleRealValues[t]); }
 	inline bool  GetBoolRule(BoolType t) const { return(m_RuleBoolValues[t]); }
+	inline Hook  GetHookRule(HookType t) const { return(m_RuleHookValues[t]); }
 
 	//management routines
 	static const char *GetRuleName(IntType  t) { return(s_RuleInfo[t].name); }
 	static const char *GetRuleName(RealType t) { return(s_RuleInfo[t+_IntRuleCount].name); }
 	static const char *GetRuleName(BoolType t) { return(s_RuleInfo[t+_IntRuleCount+_RealRuleCount].name); }
+	static const char *GetRuleName(HookType t) { return(s_RuleInfo[t+_IntRuleCount+_RealRuleCount+_BoolRuleCount].name); }
 	static uint32 CountRules() { return(_RulesCount); }
 	static CategoryType FindCategory(const char *catname);
 	bool ListRules(const char *catname, std::vector<const char *> &into);
@@ -117,11 +130,13 @@
 	int 	m_RuleIntValues [_IntRuleCount ];
 	float	m_RuleRealValues[_RealRuleCount];
 	bool	m_RuleBoolValues[_BoolRuleCount];
+	Hook	m_RuleHookValues[_HookRuleCount];
 
 	typedef enum {
 		IntRule,
 		RealRule,
-		BoolRule
+		BoolRule,
+		HookRule
 	} RuleType;
 	
 	static bool _FindRule(const char *rule_name, RuleType &type_into, uint16 &index_into);
Index: Source/common/ruletypes.h
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/ruletypes.h,v
retrieving revision 1.3.2.8
diff -u -b -B -r1.3.2.8 ruletypes.h
--- Source/common/ruletypes.h	21 Feb 2007 16:04:19 -0000	1.3.2.8
+++ Source/common/ruletypes.h	19 Jan 2008 12:42:10 -0000
@@ -13,6 +13,9 @@
 #ifndef RULE_BOOL
 #define RULE_BOOL(cat, rule, default_value)
 #endif
+#ifndef RULE_HOOK
+#define RULE_HOOK(cat, rule, default_value)
+#endif
 #ifndef RULE_CATEGORY_END
 #define RULE_CATEGORY_END()
 #endif
@@ -71,10 +74,16 @@
 RULE_REAL ( Combat, ClientBaseCritChance, 0.0 ) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance.
 RULE_CATEGORY_END()
 
+RULE_CATEGORY( CharacterCreation )
+RULE_HOOK ( CharacterCreation, ChangeCreationInfo, (Hook) default_CharacterCreation_ChangeCreationInfo )
+RULE_CATEGORY_END()
+
+
 #undef RULE_CATEGORY
 #undef RULE_INT
 #undef RULE_REAL
 #undef RULE_BOOL
+#undef RULE_HOOK
 #undef RULE_CATEGORY_END
Reply With Quote
  #3  
Old 01-19-2008, 02:51 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default CVS DIFF Part 2

Code:
Index: Source/world/client.cpp
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/world/client.cpp,v
retrieving revision 1.12.2.20
diff -u -b -B -r1.12.2.20 client.cpp
--- Source/world/client.cpp	22 Nov 2006 14:03:34 -0000	1.12.2.20
+++ Source/world/client.cpp	19 Jan 2008 09:19:57 -0000
@@ -907,6 +907,8 @@
 		return false;
 	}
 
+	((Hook_CharacterCreation_ChangeCreationInfo) RuleH(CharacterCreation, ChangeCreationInfo))(cc);
+
 	// Convert incoming cc_s to the new PlayerProfile_Struct
 	memset(&pp, 0, sizeof(PlayerProfile_Struct));	// start building the profile
 	
Index: Source/world/makefile.common
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/world/makefile.common,v
retrieving revision 1.1.2.6
diff -u -b -B -r1.1.2.6 makefile.common
--- Source/world/makefile.common	31 Oct 2006 02:02:54 -0000	1.1.2.6
+++ Source/world/makefile.common	19 Jan 2008 13:37:36 -0000
@@ -24,7 +24,7 @@
    ../common/SocketLib/HttpdForm.o ../common/SocketLib/HttpdSocket.o \
    ../common/SocketLib/MemFile.o ../common/SocketLib/Mime.o \
    ../common/SocketLib/Parse.o ../common/SocketLib/Utility.o \
-   ../common/guild_base.o wguild_mgr.o ../common/rulesys.o
+   ../common/guild_base.o wguild_mgr.o ../common/rulesys.o ../common/default_Hooks.cpp
 
 all: $(APP)
 
Index: Source/zone/makefile.common
===================================================================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/makefile.common,v
retrieving revision 1.2.2.12
diff -u -b -B -r1.2.2.12 makefile.common
--- Source/zone/makefile.common	18 Jul 2006 05:04:49 -0000	1.2.2.12
+++ Source/zone/makefile.common	19 Jan 2008 13:37:36 -0000
@@ -30,7 +30,7 @@
    ../common/EQStreamIdent.o ../common/patches/Live.o \
    zone_logsys.o ../common/BasePacket.o ../common/worldconn.o \
    ../common/EmuTCPConnection.o ../common/EmuTCPServer.o ../common/TCPServer.o \
-   ../common/guild_base.o guild_mgr.o
+   ../common/guild_base.o guild_mgr.o ../common/default_Hooks.cpp
 
 
 all: $(APP)
Reply With Quote
  #4  
Old 01-19-2008, 02:52 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default Two files that go in "common"

Code:
common/default_Hooks.h
----------------------
#ifndef DEFAULT_RULES_H_
#define DEFAULT_RULES_H_

#include "../common/eq_packet_structs.h"

/** A hook can be a function pointer of any type. So we define it as void *.
 *  Hooks are cast to their actual function type when they are called.
 */
typedef void *Hook;

/** Macro used to check the compliance of hook functions with the definition of the hook.
 *  It ensures the hook function has the correct signature.
 *  It can be very useful to avoid mistakes, and when a hook signature changes.
 *  The compiler can then issue an error.
 *  If no signature check is done, a badly-defined hook function will probably crash the server.
 *  usage: add a line after the hook function definition (in the *.cpp file)
 *    CheckHookSignature(CharacterCreation, ChangeCreationInfo, default_CharacterCreation_ChangeCreationInfo);
 */

#define CheckHookSignature(cat, rule, hook) \
	static Hook_##cat##_##rule CheckSignature_##hook = hook


/** Called during character creation right after the CharCreate structure has been checked for correctness.
 *  Allows the world builder to tweak the values sent by the client prior to the actual character creation.
 */
typedef void (*Hook_CharacterCreation_ChangeCreationInfo)(CharCreate_Struct *cc);


/** Does nothing.
 */
void default_CharacterCreation_ChangeCreationInfo(CharCreate_Struct *cc);


#endif /*DEFAULT_RULES_H_*/



common/default_Hooks.cpp
------------------------
#include "../common/debug.h"
#include "../common/default_Hooks.h"


void default_CharacterCreation_ChangeCreationInfo(CharCreate_Struct *cc)
{
};
CheckHookSignature(CharacterCreation, ChangeCreationInfo, default_CharacterCreation_ChangeCreationInfo);


I did not include the changes to the VC++ projects, I am kinda scared about the result. I will just give directives : add files common/default_Hooks.h and common/default_Hooks.cpp to the Common header/Source files of the World and the Zone projects

I will make a reply to this post to show how to implement the one hook I placed in the emulator code at the moment (a hook to tweak the character info structure coming from the Everquest client, prior to the actual character creation).
Reply With Quote
  #5  
Old 01-19-2008, 03:19 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default How to implement the Character Creation hook

To ensure the rules hook worked I included one hook, during the character creation. Why there ? Because I had problems running "zone" under Linux (my installatio's fault most certainly), so I chose one place I would easily access in "world". Plus I want to make everyone a ranger !
This hook is inserted into the character creation process, right after the server checks for cheaters (it checks whether the stats provided are possible), and before the character is actually created. Now that we are sure the guy at the other end is not cheating, we can change his character how we see fit.
This hook function signature is simple : it receives the CharInfo_Struct pointer and can fiddle with it however it wants. The function returns nothing, it works by altering the CharInfo_Struct. The hook signature is :
Code:
void default_CharacterCreation_ChangeCreationInfo(CharCreate_Struct *cc);
The default implementation does nothing. The character is created as the player asked.
My Hero server forces him to be a ranger !
To implement the hook (the first hook is of course the longest), you must create a new project for your rules shared library. On Windows I created a new project named "Hero", and made every single setting identical to the EmuSharedMem project, as I was not sure how to create a proper DLL. On Unix, you also make a new directory and copy in the makefile and makefile.common files from EmuSharedMem, that you adapt to your library (rename EmuSharedMem to Hero in my case).
You then need one hero_Hooks.cpp file for your custom hook, and on Windows you need an additional DLLMain.cpp file. You can copy the one from EmuSharedMem, it works fine.

Here is the contents of the hero_Hooks.cpp file. It ensures the function is exported as a DLL on Windows, implements the hook signature, and makes the character a ranger !
Code:
#include "../common/debug.h"
#include "../common/eq_packet_structs.h"
#include "../common/default_Hooks.h"
#include "../common/classes.h"

#ifdef WIN32
	#define exportfunc extern "C" __declspec(dllexport)
#else
	#define exportfunc extern "C"
#endif

/** Makes all newly-created characters Rangers - It is the class chosen for heroes as it can cast spells
 *  and have dual-wield. After all, aren't all rangers heroes ?
 */
exportfunc void hero_CharacterCreation_ChangeCreationInfo(CharCreate_Struct *cc)
{	cc->class_ = RANGER;
};
CheckHookSignature(CharacterCreation, ChangeCreationInfo, hero_CharacterCreation_ChangeCreationInfo);
Note that the CheckHookSignature macro call is not mandatory, but it will warn you in case the hook signature changes (the compiler will report an error).

Then you compile, copy the Hero.[dll|so] library file with his friends, the world and zone executable in your server directory, and you run your server. Guess what, nothing has changed (yet).

You need to tell the emulator that you want to use this new hook for your ruleset. If you have never played with rules before what's coming next will be rough, but I will not try to explain how rules work. You can certainly find this information on the site.

Run the following commands against your database to enable the new hook :
Code:
ALTER TABLE rule_values MODIFY rule_value VARCHAR(128);

INSERT INTO rule_sets values ('2', 'hero');

INSERT INTO rule_values VALUES ('2','CharacterCreation:ChangeCreationInfo','Hero:hero_CharacterCreation_ChangeCreationInfo');

INSERT INTO variables(varname, value, information) VALUES ('RuleSet', 'hero', 'Rule set currently in use for this server');
Note that it will alter your database to allow for long rule values. You could very well stay with the old rule values column size, but you will be forced to use very short library/function names, so that the value can fit in the column. Yes, that means 9 characters total for the library plus the function name (the column is size 10, and you have to include a colon in there). If you do not want to alter your database, rename the function something like "h001", and use "Hero:h001" for the rule value in the query above. It should work fine.

You need to restart (or start) your world server for the rule to be re-read from the database. Once your world server is up, fire up the EQ client and create a new character, preferably not a ranger. You will get a ranger anyway, even if your race does not allow it.

I hope you like rangers
Reply With Quote
  #6  
Old 01-21-2008, 08:59 PM
KLS
Administrator
 
Join Date: Sep 2006
Posts: 1,348
Default

I'll see if I can't point FNW at this when I can, I don't think my level of expertise really covers this kind of thing. Don't get discouraged!
Reply With Quote
  #7  
Old 01-22-2008, 11:53 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default

I have been starting to add a couple hooks for my own use, used in the "zone" process. That is when I realized that you do not want to mix hooks for "zone" and hooks for "world" at the same time. Doing so would pull unwanted files (to "world" mostly), like "mob.cpp", if you need to include "mob.h" to implement your hook.

Of course there is a solution

This solution requires a minor change in "ruletypes.h" to include some #ifdef directives, to only compile hooks for "world" when you compile world, and vice-versa.
It also requires some #ifdef in "default_Hooks.h" and "default_Hooks.cpp" to do the same. As the file is new I can probably just dump a new version.
Finally it requires building not one shared library for the custom hooks, but two : one to be loaded by "world", one to be loaded by "zone".

I still have a remaining issue with warnings appearing when "world" and "zone" load, because both processes try to load ALL the rules, not just those pertaining to them. I can probably find a solution by this week-end but right now I am going to bed

To sum it up, I should post the fixed version around this week-end. Nothing outstanding changed, but some more #ifdef to cleanly separate things will be added.

So I keep my hopes high !
Reply With Quote
  #8  
Old 02-28-2008, 08:57 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default

I am sorry for not answering earlier but work has been quite "entertaining" lately, and my time on EQEmu went low as a consequence. Still I worked some more on this topic (and on server/quest implementation) and found out that it was not as straightforward as expected, as usual with computer engineering.

The major problem I encountered is that you cannot pass just any argument you like to the hook functions (like a Client instance) and use it, as otherwise you would drag all the rest of the cpp files with your hooks. This limits the scope of the hooks.

The second problem (a consequence of the previous one) is that you do not want to move the existing behaviour out of the current functions. Instead it is easier to define the hooks as 'NULL' by default, and only call them if they are set in the rules, with the list of real parameters you need (ability scores etc) not the full objects containing them. A simple "if" does the trick at the point where the hook is introduced.

In the end it proved usable, and useful for me. I could also work-around the problem with hooks specific to "world" and "zone" by putting some prefix in the rule values.

Before submitting again what would be the final version I need to merge the new code written for EQRmu since my first submission. Hopefully I can do it this week-end. It will also show me whether implementing these hooks helped in maintaining parallel code for custom rules, while keeping compatibility with changes done on EQEmu as a whole.
Reply With Quote
  #9  
Old 02-28-2008, 11:03 PM
trevius's Avatar
trevius
Developer
 
Join Date: Aug 2006
Location: USA
Posts: 5,946
Default

Sounds interesting! I would love to have the ability to have more custom rules. At the very least, it sounds like Bulle could be a potential candidate for helping on the Emu dev team

I believe the devs have been working to add more rules in the future. I read all of these posts, but I am not much of a coder, so I don't fully understand how hooks differ from the normal way that KLS, Wildcardx, FNW and the team have been adding new rules. To me, it seems like hooks are just another way to add more custom rules. I am not sure how the devs currently do it, but their way seems to work well. As long as everything works in the end code, I am happy though, lol.
__________________
Trevazar/Trevius Owner of: Storm Haven
Everquest Emulator FAQ (Frequently Asked Questions) - Read It!
Reply With Quote
  #10  
Old 02-29-2008, 07:22 AM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default

To put it simply, the difference between a rule and a hook is how you write it, and hence the power of it.

A rule is merely a number (simplifying here) allowing you to tweak a pre-set behaviour, which a developer has coded. It requires no special skill to use except understanding what the number's impact is.

A hook is a portion of code (let's say 10 lines long) that is called instead or in addition of a regular server computation. It requires you to know how to code a C++ function, but it is much more powerful. In the spirit it is like a short script, just it is in C++, so you need to compile the code yada yada.

Best is to use an example : resist chance for a spell.
Current rules allow you to specify a resist base percentage (ResistChance), a resist multiplier (ResisstMod), a level difference after which no resist is possible for the PC (AutoResistDiff) and the proportion of partial resists (PartialHitchance). The behaviour using these variables is written for you, you cannot change it.
On my test server, instead, I allowed replacing the full resist chance computation with a hook. By defining the hook and implementing in C++ as a function accepting a few parameters and returning the resist chance, I can compute it as I want. In my case I do something on the line of the Shards of Dalaya server, the CHA of the caster influences the resist chance (and the WIS of the target decreases it).

Why not just replace the existing server code ? Because keeping the changes to the original server code is important if you want to integrate new changes done on the main server easily in your custom server. Once the hook is in place (and it can be put in the main server code as by default it changes nothing to the "live-like" server behaviour) custom server developers do not need to touch the original code to significantly tune some rules.

That was my goal. I wanted to implement a score of custom rules, but still keep my server "close to original" EQEmu. In the end I could do most of my changes with hooks and implemented :
- overridding some player choices during character creation
- rewriting the resist chance for spells
- rewriting the melee to-hit chance, damage, mitigation
- rewriting the mana regen
- rewriting the fizzle chance
- rewriting the AC computation (the client cannot show the server-used score, too bad but inevitable)
- rewriting the EXP per level

With minimal and not disrupting changes to the current server code.

I hope it helped explain the difference between rule variables, hooks, and utter server code change. The idea is that hooks could be part of the regular server code "as options ready to be used", on the developer choice, and without having such checks like "if server is guild wars". It would be instead : if designer wants something special we call it, otherwise "live" implementation.
Reply With Quote
  #11  
Old 03-01-2008, 08:49 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default New hook version + Source/common/rulesys.cpp

Here is the latest version of the hooks implementation. This is meant to replace what has been already submitted in this post.
First post is the code enabling the use of hooks as rules. It defines some hook rules (the ones I use), but does not actually "wire" them in the server code.
In short, adding this code changes nothing to the server behaviour, it just introduces the capability. Next posts will add the code to put all the hooks I use in place, which the server development team may want to include or not.
Hooks not wired can be removed from ruletypes.h altogether without harm (those are all the RULE_HOOK entries).
A word of warning : I have not checked the Unix version in a long while, so do not rely on it. If this code makes it to the official server then I will take the time to complete/fix the Unix part.
On the other hand I have not had to touch the Windows version in a little while, so it is probably as stable as I can make it for the moment.

Index: Source/common/rulesys.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/rulesys.cpp,v
retrieving revision 1.2.2.2
diff -u -b -B -r1.2.2.2 rulesys.cpp
--- Source/common/rulesys.cpp 31 Oct 2006 02:02:54 -0000 1.2.2.2
+++ Source/common/rulesys.cpp 2 Mar 2008 08:41:01 -0000
@@ -20,7 +20,10 @@
#include "logsys.h"
#include "database.h"
#include "MiscFunctions.h"
-
+#ifndef WIN32
+/* On Linux, include the headers for libdl for dynamic library loading */
+#include <dlfcn.h>
+#endif /* !WIN32 */
/*

FatherNitwit: Added new rules subsystem to allow game rules to be changed
@@ -73,7 +76,7 @@
"InvalidCategory"
};

-const RuleManager::RuleInfo RuleManager::s_RuleInfo[_IntRuleCount+_RealRuleCount+_BoolRuleCount+1] = {
+const RuleManager::RuleInfo RuleManager::s_RuleInfo[_IntRuleCount+_RealRuleCount+_BoolRuleCount+_HookR uleCount+1] = {
/* this is done in three steps so we can reliably get to them by index*/
#define RULE_INT(cat, rule, default_value) \
{ #cat ":" #rule, Category__##cat, IntRule, Int__##rule },
@@ -84,6 +87,9 @@
#define RULE_BOOL(cat, rule, default_value) \
{ #cat ":" #rule, Category__##cat, BoolRule, Bool__##rule },
#include "ruletypes.h"
+ #define RULE_HOOK(cat, rule, default_value) \
+ { #cat ":" #rule, Category__##cat, HookRule, Hook__##rule },
+ #include "ruletypes.h"
{ "Invalid Rule", _CatCount, IntRule }
};

@@ -135,6 +141,20 @@
if(rule_name == NULL || rule_value == NULL)
return(false);

+ /* World ignores rules starting with (zone), zone ignores rules starting with (world), both skip (world) or (zone) if at the beginning */
+#ifdef WORLD
+ if(strncmp(rule_name, "(zone) ", strlen("(zone) ")) == 0)
+ return true;
+#endif /* WORLD */
+#ifdef ZONE
+ if(strncmp(rule_name, "(world) ", strlen("(world) ")) == 0)
+ return true;
+#endif /* ZONE */
+ if(strncmp(rule_name, "(zone) ", strlen("(zone) ")) == 0)
+ rule_name = rule_name + strlen("(zone) ");
+ else if(strncmp(rule_name, "(world) ", strlen("(world) ")) == 0)
+ rule_name = rule_name + strlen("(world) ");
+
RuleType type;
uint16 index;
if(!_FindRule(rule_name, type, index))
@@ -149,6 +169,57 @@
m_RuleRealValues[index] = atof(rule_value);
_log(RULES__CHANGE, "Set rule %s to value %.13f", rule_name, m_RuleRealValues[index]);
break;
+ case HookRule:
+ { char *fLibraryName = NULL, *fFunctionName = NULL;
+ Hook ThisHook = NULL;
+
+ const char *Colon = strchr(rule_value, ':');
+ if(Colon == NULL)
+ _log(RULES__ERROR, "Badly-formed hook value '%s' (rule '%s'). It must contain a colon (. Ignoring this hook", rule_value, rule_name);
+ else
+ { fLibraryName = (char *) malloc(Colon - rule_value + 128 + 1); /* 128 = let's assume it is more than enough to append a file extension to the library if needed */
+ strncpy(fLibraryName, rule_value, Colon - rule_value); fLibraryName[Colon - rule_value] = '\0';
+ fFunctionName = (char *) malloc(strlen(rule_value) - (Colon - rule_value + 1) + 1);
+ strcpy(fFunctionName, Colon + 1);
+ }
+#ifdef WIN32
+ HMODULE ThisLibrary = NULL;
+ if(fLibraryName != NULL)
+ { strcat(fLibraryName, ".dll");
+ ThisLibrary = LoadLibrary(fLibraryName);
+ if(ThisLibrary == NULL)
+ _log(RULES__ERROR, "Cannot load library '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fLibraryName, rule_value, rule_name);
+ }
+
+ if(ThisLibrary != NULL)
+ { ThisHook = GetProcAddress(ThisLibrary, fFunctionName);
+ if(ThisHook == NULL)
+ _log(RULES__ERROR, "Cannot load library function '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fFunctionName, rule_value, rule_name);
+ }
+#else /* WIN32 */
+ void *ThisLibrary = NULL;
+ if(fLibraryName != NULL)
+ { strcat(fLibraryName, ".so");
+ ThisLibrary = dlopen(fLibraryName, RTLD_NOW | RTLD_GLOBAL);
+ if(ThisLibrary == NULL)
+ _log(RULES__ERROR, "Cannot load library '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fLibraryName, rule_value, rule_name);
+ }
+
+ if(ThisLibrary != NULL)
+ { ThisHook = dlsym(ThisLibrary, fFunctionName);
+ if(ThisHook == NULL)
+ _log(RULES__ERROR, "Cannot load library function '%s' needed by hook '%s' (rule '%s'). Ignoring this hook", fFunctionName, rule_value, rule_name);
+ }
+ /*_log(RULES__ERROR, "The rule hooks are not yet implemented on this platform. Ignoring hook '%s' for fule '%s'", rule_value, rule_name);*/
+#endif /* WIN32 */
+ if(ThisHook != NULL)
+ { m_RuleHookValues[index] = ThisHook;
+ _log(RULES__CHANGE, "Set rule %s to value %d", rule_name, (int) m_RuleHookValues[index]);
+ }
+ if(fLibraryName != NULL) { free(fLibraryName); fLibraryName = NULL; }
+ if(fFunctionName != NULL) { free(fFunctionName); fFunctionName = NULL; }
+ break;
+ }
case BoolRule:
bool val = false;
if(!strcasecmp(rule_value, "on") || !strcasecmp(rule_value, "true") || !strcasecmp(rule_value, "yes") || !strcasecmp(rule_value, "enabled") || !strcmp(rule_value, "1"))
@@ -172,6 +243,8 @@
m_RuleRealValues[ Real__##rule ] = default_value;
#define RULE_BOOL(cat, rule, default_value) \
m_RuleBoolValues[ Bool__##rule ] = default_value;
+ #define RULE_HOOK(cat, rule, default_value) \
+ m_RuleHookValues[ Hook__##rule ] = default_value;
#include "ruletypes.h"
}

@@ -202,6 +275,8 @@
return(s_RuleInfo[index+_IntRuleCount].name);
case BoolRule:
return(s_RuleInfo[index+_IntRuleCount+_RealRuleCount].name);
+ case HookRule:
+ return(s_RuleInfo[index+_IntRuleCount+_RealRuleCount+_BoolRuleCount].name);
}
//should never happen
return("InvalidRule??");
@@ -236,6 +311,9 @@
for(r = 0; r < _BoolRuleCount; r++) {
_SaveRule(db, BoolRule, r);
}
+ for(r = 0; r < _HookRuleCount; r++) {
+ _SaveRule(db, HookRule, r);
+ }
}


@@ -280,7 +358,10 @@
void RuleManager::_SaveRule(Database *db, RuleType type, uint16 index) {
char vstr[16];

- switch(type) {
+ if(type == HookRule)
+ _log(RULES__ERROR, "Impossible to save hook rule %s in the database. Hook rule saving is not supported", _GetRuleName(type, index));
+ else
+ { switch(type) {
case IntRule:
sprintf(vstr, "%d", m_RuleIntValues[index]);
break;
@@ -302,6 +383,7 @@
_log(RULES__ERROR, "Fauled to set rule in the database: %s: %s", query,errbuf);
}
safe_delete_array(query);
+ }
}
Reply With Quote
  #12  
Old 03-01-2008, 08:49 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default common/rulesys.h

Index: Source/common/rulesys.h
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/rulesys.h,v
retrieving revision 1.2.2.2
diff -u -b -B -r1.2.2.2 rulesys.h
--- Source/common/rulesys.h 31 Oct 2006 02:02:54 -0000 1.2.2.2
+++ Source/common/rulesys.h 2 Mar 2008 08:41:02 -0000
@@ -35,6 +35,8 @@
rules->GetRealRule( RuleManager::Real__##rule )
#define RuleB(cat, rule) \
rules->GetBoolRule( RuleManager::Bool__##rule )
+#define RuleH(cat, rule) \
+ rules->GetHookRule( RuleManager::Hook__##rule )


#include <vector>
@@ -42,6 +44,7 @@
#include <map>

#include "types.h"
+#include "../common/default_Hooks.h"

class Database;

@@ -70,6 +73,13 @@
} BoolType;

typedef enum {
+ #define RULE_HOOK(cat, rule, default_value) \
+ Hook__##rule,
+ #include "ruletypes.h"
+ _HookRuleCount
+ } HookType;
+
+ typedef enum {
#define RULE_CATEGORY(catname) \
Category__##catname,
#include "ruletypes.h"
@@ -79,9 +89,10 @@
static const IntType InvalidInt = _IntRuleCount;
static const RealType InvalidReal = _RealRuleCount;
static const BoolType InvalidBool = _BoolRuleCount;
+ static const HookType InvalidHook = _HookRuleCount;
static const CategoryType InvalidCategory = _CatCount;

- static const uint32 _RulesCount = _IntRuleCount+_RealRuleCount+_BoolRuleCount;
+ static const uint32 _RulesCount = _IntRuleCount+_RealRuleCount+_BoolRuleCount+_HookR uleCount;

RuleManager();

@@ -89,11 +100,13 @@
inline int GetIntRule (IntType t) const { return(m_RuleIntValues[t] ); }
inline float GetRealRule(RealType t) const { return(m_RuleRealValues[t]); }
inline bool GetBoolRule(BoolType t) const { return(m_RuleBoolValues[t]); }
+ inline Hook GetHookRule(HookType t) const { return(m_RuleHookValues[t]); }

//management routines
static const char *GetRuleName(IntType t) { return(s_RuleInfo[t].name); }
static const char *GetRuleName(RealType t) { return(s_RuleInfo[t+_IntRuleCount].name); }
static const char *GetRuleName(BoolType t) { return(s_RuleInfo[t+_IntRuleCount+_RealRuleCount].name); }
+ static const char *GetRuleName(HookType t) { return(s_RuleInfo[t+_IntRuleCount+_RealRuleCount+_BoolRuleCount].name); }
static uint32 CountRules() { return(_RulesCount); }
static CategoryType FindCategory(const char *catname);
bool ListRules(const char *catname, std::vector<const char *> &into);
@@ -117,11 +130,13 @@
int m_RuleIntValues [_IntRuleCount ];
float m_RuleRealValues[_RealRuleCount];
bool m_RuleBoolValues[_BoolRuleCount];
+ Hook m_RuleHookValues[_HookRuleCount];

typedef enum {
IntRule,
RealRule,
- BoolRule
+ BoolRule,
+ HookRule
} RuleType;

static bool _FindRule(const char *rule_name, RuleType &type_into, uint16 &index_into);
Reply With Quote
  #13  
Old 03-01-2008, 08:50 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default common/ruletypes.h

Index: Source/common/ruletypes.h
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/common/ruletypes.h,v
retrieving revision 1.3.2.8
diff -u -b -B -r1.3.2.8 ruletypes.h
--- Source/common/ruletypes.h 21 Feb 2007 16:04:19 -0000 1.3.2.8
+++ Source/common/ruletypes.h 2 Mar 2008 08:41:02 -0000
@@ -13,6 +13,9 @@
#ifndef RULE_BOOL
#define RULE_BOOL(cat, rule, default_value)
#endif
+#ifndef RULE_HOOK
+#define RULE_HOOK(cat, rule, default_value)
+#endif
#ifndef RULE_CATEGORY_END
#define RULE_CATEGORY_END()
#endif
@@ -20,6 +23,15 @@



+RULE_CATEGORY( Attack )
+#ifdef ZONE
+RULE_HOOK( Attack, ToHitChance, (Hook) NULL )
+RULE_HOOK( Attack, ClientDamageRange, (Hook) NULL )
+RULE_HOOK( Attack, NpcDamageRange, (Hook) NULL )
+RULE_HOOK( Attack, Mitigation, (Hook) NULL )
+#endif /* ZONE */
+RULE_CATEGORY_END()
+
RULE_CATEGORY( Character )
RULE_INT ( Character, MaxLevel, 65 )
RULE_INT ( Character, DeathExpLossLevel, 6 )
@@ -32,6 +44,13 @@
RULE_INT ( Character, ManaRegenMultiplier, 100)
RULE_INT ( Character, EnduranceRegenMultiplier, 100)
RULE_INT ( Character, ConsumptionMultiplier, 100) //item's hunger restored = this value * item's food level, 100 = normal, 50 = people eat 2x as fast, 200 = people eat 2x as slow
+#ifdef ZONE
+RULE_HOOK( Character, PostAddItemBonuses, (Hook) NULL )
+RULE_HOOK( Character, CalcAC, (Hook) NULL )
+RULE_HOOK( Character, EXPForLevel, (Hook) NULL )
+RULE_HOOK( Character, ManaRegen, (Hook) NULL )
+RULE_HOOK( Character, ChanceOfSkillIncrease, (Hook) NULL )
+#endif /* ZONE */
RULE_CATEGORY_END()

RULE_CATEGORY( Guild )
@@ -61,6 +80,10 @@
RULE_REAL (Spells, ResistChance, 2.0) //chance to resist given no resists and same level
RULE_REAL (Spells, ResistMod, 0.40) //multiplier, chance to resist = this * ResistAmount
RULE_REAL (Spells, PartialHitChance, 0.7) //The chance when a spell is resisted that it will partial hit.
+#ifdef ZONE
+RULE_HOOK( Spells, FizzleChance, (Hook) NULL )
+RULE_HOOK( Spells, FinalResistChance, (Hook) NULL )
+#endif /* ZONE */
RULE_CATEGORY_END()

RULE_CATEGORY( Combat )
@@ -71,10 +94,28 @@
RULE_REAL ( Combat, ClientBaseCritChance, 0.0 ) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance.
RULE_CATEGORY_END()

+RULE_CATEGORY( CharacterCreation )
+#ifdef WORLD
+RULE_HOOK( CharacterCreation, ChangeCreationInfo, (Hook) NULL )
+#endif /* WORLD */
+RULE_CATEGORY_END()
+
+RULE_CATEGORY( Random )
+RULE_HOOK( Random, RandomFloat, (Hook) NULL )
+RULE_CATEGORY_END()
+
+RULE_CATEGORY( XP )
+#ifdef ZONE
+RULE_HOOK( XP, PreChange, (Hook) NULL )
+#endif /* ZONE */
+RULE_CATEGORY_END()
+
+
#undef RULE_CATEGORY
#undef RULE_INT
#undef RULE_REAL
#undef RULE_BOOL
+#undef RULE_HOOK
#undef RULE_CATEGORY_END
Reply With Quote
  #14  
Old 03-01-2008, 08:55 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default Hook 1 : Tweak Player choices on character creation

Adds the code to the server to process this hook. No effect until the hook is actually implemented in a shared library and activated in the database.

Index: client.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/world/client.cpp,v
retrieving revision 1.12.2.20
diff -u -b -B -r1.12.2.20 client.cpp
--- client.cpp 22 Nov 2006 14:03:34 -0000 1.12.2.20
+++ client.cpp 2 Mar 2008 08:53:44 -0000
@@ -907,6 +907,9 @@
return false;
}

+ if(RuleH(CharacterCreation, ChangeCreationInfo) != NULL)
+ ((Hook_CharacterCreation_ChangeCreationInfo) RuleH(CharacterCreation, ChangeCreationInfo))(cc);
+
// Convert incoming cc_s to the new PlayerProfile_Struct
memset(&pp, 0, sizeof(PlayerProfile_Struct)); // start building the profile
Reply With Quote
  #15  
Old 03-01-2008, 09:00 PM
Bulle
Hill Giant
 
Join Date: Jan 2008
Posts: 102
Default Hooks 2 : custom combat computations

Tweak the to-hit chance
Tweak the AC mitigation effect
Tweak the client and NPC damage ranges


Index: attack.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/attack.cpp,v
retrieving revision 1.28.2.57
diff -u -b -B -r1.28.2.57 attack.cpp
--- attack.cpp 21 Feb 2007 16:04:20 -0000 1.28.2.57
+++ attack.cpp 2 Mar 2008 08:58:41 -0000
@@ -166,7 +166,25 @@
int8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1;
int8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1;

- //
+if(RuleH(Attack, ToHitChance) != NULL)
+{ struct _Hook_Attack_ToHitChance_Parameters Parameters;
+ Parameters.IsPvp = pvpmode;
+ Parameters.AttackerIsClient = attacker->IsClient();
+ Parameters.AttackerLevel = attacker_level;
+ Parameters.AttackerDex = attacker->GetDEX();
+ if(Parameters.AttackerIsClient)
+ { Parameters.AttackerWeaponSkill = attacker->GetSkill(skillinuse);
+ Parameters.AttackerOffense = attacker->GetSkill(OFFENSE);
+ }
+ Parameters.DefenderIsClient = defender->IsClient();
+ Parameters.DefenderLevel = defender_level;
+ Parameters.DefenderAgi = defender->GetAGI();
+ if(Parameters.DefenderIsClient)
+ Parameters.DefenderDefense = defender->GetSkill(DEFENSE);
+ chancetohit = ((Hook_Attack_ToHitChance) RuleH(Attack, ToHitChance))(&Parameters);
+}
+else
+{ //
// we start by giving them a base chance to hit
//
chancetohit = 50;
@@ -232,6 +250,7 @@
chancetohit += (float) ((float)attacker_dex * 0.15f);

mlog(COMBAT__TOHIT, "Applied Defending AGI (%d) and Attacking (DEX-50=%d) yeilding %.2f", defender_agi, attacker_dex, chancetohit);
+}

//divided these bonuses by 4... higher level battles were basically always 95%
//hit chance because of this 50% bonus...
@@ -319,7 +338,8 @@
//if chance to hit is crazy high, that means a discipline is in use, and let it stay there
} else if(chancetohit > 95) {
chancetohit = 95;
- } else if(chancetohit < 30) {
+ }
+ if(RuleH(Attack, ToHitChance) == NULL && chancetohit < 30) {
chancetohit = 30;
}

@@ -514,7 +534,24 @@
// Scorpious2k: Include AC in the calculation

// use serverop variables to set values
- int myac = GetAC();
+if(RuleH(Attack, Mitigation) != NULL)
+{ struct _Hook_Attack_Mitigation_Parameters Parameters;
+ Parameters.AttackerIsClient = other->IsClient();
+ Parameters.AttackerLevel = other->GetLevel();
+ if(Parameters.AttackerIsClient)
+ Parameters.AttackerOffense = GetSkill(OFFENSE);
+
+ Parameters.DefenderIsClient = other->IsClient();
+ Parameters.DefenderLevel = GetLevel();
+ Parameters.DefenderAC = GetAC();
+ if(Parameters.DefenderIsClient)
+ Parameters.DefenderDefense = GetSkill(DEFENSE);
+ Parameters.Damage = damage;
+ ((Hook_Attack_Mitigation) RuleH(Attack, Mitigation))(&Parameters);
+ damage = Parameters.Damage;
+}
+else
+{ int myac = GetAC();
if (damage > 0 && myac > 0) {
int acfail=1000;
char tmp[10];
@@ -551,6 +588,7 @@
mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail);
}
}
+}

int aaMit = 0;
switch(GetAA(aaCombatStability)){
@@ -780,7 +818,20 @@
mlog(COMBAT__ATTACKS, "Failed a finishing blow: AA at %d, other level %d, roll %.1f", aa_item, other->GetLevel(), tempchancerand);
}

- min_hit = 1;
+if(RuleH(Attack, ClientDamageRange) != NULL)
+{ struct _Hook_Attack_ClientDamageRange_Parameters Parameters;
+ Parameters.AttackerStr = this->GetSTR();
+ Parameters.AttackerLevel = this->GetLevel();
+ Parameters.AttackerWeaponDamage = weapon_damage;
+ Parameters.AttackerWeaponDelay = (weapon_item == NULL ? 20 : weapon_item->Delay);
+
+ Parameters.DefenderLevel = other->GetLevel();
+ ((Hook_Attack_ClientDamageRange) RuleH(Attack, ClientDamageRange))(&Parameters);
+ min_hit = Parameters.MinHit;
+ max_hit = Parameters.MaxHit;
+}
+else
+{ min_hit = 1;
//This needs to be researched, it seems terribly off. Changed to use offense skill for now instead of weapon since we know that is correct
max_hit = (weapon_damage * (((GetSTR()*20) + (GetSkill(OFFENSE)*15) + (mylevel*10)) / 1000)); // Apply damage formula

@@ -790,6 +841,7 @@
min_hit += damage_bonus;
max_hit += damage_bonus;
}
+}

min_hit = min_hit * (100 + itembonuses.MinDamageModifier + spellbonuses.MinDamageModifier) / 100;

@@ -1295,7 +1347,19 @@
otherlevel = otherlevel ? otherlevel : 1;
mylevel = mylevel ? mylevel : 1;

- //instead of calcing damage in floats lets just go straight to ints
+if(RuleH(Attack, ClientDamageRange) != NULL)
+{ struct _Hook_Attack_NpcDamageRange_Parameters Parameters;
+ Parameters.AttackerStr = this->GetSTR();
+ Parameters.AttackerLevel = this->GetLevel();
+ Parameters.AttackerMaxDamage = max_dmg;
+ Parameters.AttackerDelay = (int) (36.0 * (100.0f + attack_speed) / 100.0);
+
+ Parameters.DefenderLevel = other->GetLevel();
+ ((Hook_Attack_NpcDamageRange) RuleH(Attack, NpcDamageRange))(&Parameters);
+ damage = MakeRandomInt(Parameters.MinHit, Parameters.MaxHit);
+}
+else
+{ //instead of calcing damage in floats lets just go straight to ints
damage = MakeRandomInt(min_dmg, max_dmg);

//check if we're hitting above our max or below it.
@@ -1307,6 +1371,7 @@
mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, max_dmg);
damage = max_dmg;
}
+}

//THIS IS WHERE WE CHECK TO SEE IF WE HIT:
if(other->IsClient() && other->CastToClient()->IsSitting()) {
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 07:16 AM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3