View Full Version : Full function-hooks for rules (implemented)
Bulle
01-19-2008, 02:47 AM
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.
Bulle
01-19-2008, 02:50 AM
? 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+_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 }
};
@@ -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+_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);
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
Bulle
01-19-2008, 02:51 AM
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)
Bulle
01-19-2008, 02:52 AM
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)(CharC reate_Struct *cc);
/** Does nothing.
*/
void default_CharacterCreation_ChangeCreationInfo(CharC reate_Struct *cc);
#endif /*DEFAULT_RULES_H_*/
common/default_Hooks.cpp
------------------------
#include "../common/debug.h"
#include "../common/default_Hooks.h"
void default_CharacterCreation_ChangeCreationInfo(CharC reate_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).
Bulle
01-19-2008, 03:19 AM
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 :
void default_CharacterCreation_ChangeCreationInfo(CharC reate_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 !
#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(CharCrea te_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 :
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 :)
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!
Bulle
01-22-2008, 11:53 AM
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 !
Bulle
02-28-2008, 08:57 AM
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.
trevius
02-28-2008, 11:03 PM
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 :D
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.
Bulle
02-29-2008, 07:22 AM
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.
Bulle
03-01-2008, 08:49 PM
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);
+ }
}
Bulle
03-01-2008, 08:49 PM
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);
Bulle
03-01-2008, 08:50 PM
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
Bulle
03-01-2008, 08:55 PM
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
Bulle
03-01-2008, 09:00 PM
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()) {
Bulle
03-01-2008, 09:01 PM
One word about the previous hooks (and some coming others) : I did not indent the hook code as should normally be. the reason is that it avoids getting changes in diffs on all the regular code I enclosed in "ifs".
Bulle
03-01-2008, 09:03 PM
Index: bonuses.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/bonuses.cpp,v
retrieving revision 1.7.2.15
diff -u -b -B -r1.7.2.15 bonuses.cpp
--- bonuses.cpp 21 Feb 2007 16:04:20 -0000 1.7.2.15
+++ bonuses.cpp 2 Mar 2008 09:06:47 -0000
@@ -25,6 +25,7 @@
#include "../common/skills.h"
#include "../common/bodytypes.h"
#include "../common/classes.h"
+#include "../common/rulesys.h"
#include <math.h>
#include <assert.h>
#ifndef WIN32
@@ -277,6 +278,9 @@
for(i = 0; i < MAX_AUGMENT_SLOTS; i++) {
AddItemBonuses(inst->GetAugment(i),newbon);
}
+
+ if(RuleH(Character, PostAddItemBonuses) != NULL)
+ ((Hook_Character_PostAddItemBonuses) RuleH(Character, PostAddItemBonuses))(inst, newbon);
}
void Client::CalcEdibleBonuses(StatBonuses* newbon) {
Bulle
03-01-2008, 09:08 PM
Index: client.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/client.cpp,v
retrieving revision 1.36.2.51
diff -u -b -B -r1.36.2.51 client.cpp
--- client.cpp 21 Feb 2007 16:04:20 -0000 1.36.2.51
+++ client.cpp 2 Mar 2008 09:08:20 -0000
@@ -1654,9 +1654,15 @@
if (skillval < maxskill)
{
// the higher your current skill level, the harder it is
- sint16 Chance = 10 + chancemodi + ((252 - skillval) / 20);
+ sint16 Chance;
+if(RuleH(Character, ChanceOfSkillIncrease) != NULL)
+ Chance = ((Hook_Character_ChanceOfSkillIncrease) RuleH(Character, ChanceOfSkillIncrease))(skillid, skillval, maxskill, chancemodi);
+else
+{
+ Chance = 10 + chancemodi + ((252 - skillval) / 20);
if (Chance < 1)
Chance = 1; // Make it always possible
+}
if(MakeRandomFloat(0, 99) < Chance)
{
SetSkill(skillid, GetRawSkill(skillid) + 1);
Bulle
03-01-2008, 09:12 PM
Index: client_mods.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/client_mods.cpp,v
retrieving revision 1.6.4.19
diff -u -b -B -r1.6.4.19 client_mods.cpp
--- client_mods.cpp 21 Feb 2007 16:04:20 -0000 1.6.4.19
+++ client_mods.cpp 2 Mar 2008 09:11:32 -0000
@@ -28,6 +28,7 @@
#include "../common/moremath.h"
#include "../common/guilds.h"
#include "../common/logsys.h"
+#include "../common/rulesys.h"
#include "StringIDs.h"
#include "NpcAI.h"
@@ -714,7 +715,10 @@
// AC from spells are not included (cant even cast spells yet..)
sint16 Client::CalcAC() {
- // new formula
+if(RuleH(Character, CalcAC) != NULL)
+ AC = ((Hook_Character_CalcAC) RuleH(Character, CalcAC))(GetLevel(), GetSkill(DEFENSE), itembonuses.AC, spellbonuses.AC);
+else
+{ // new formula
int avoidance = 0;
avoidance = (acmod() + ((GetSkill(DEFENSE)*16)/9));
if (avoidance < 0)
@@ -749,8 +753,8 @@
//spell AC bonuses are added directly to natural total
displayed += spellbonuses.AC;
-
AC = displayed;
+}
return(AC);
}
Bulle
03-01-2008, 09:15 PM
Index: client_process.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/client_process.cpp,v
retrieving revision 1.50.2.23
diff -u -b -B -r1.50.2.23 client_process.cpp
--- client_process.cpp 21 Feb 2007 16:04:21 -0000 1.50.2.23
+++ client_process.cpp 2 Mar 2008 09:13:34 -0000
@@ -1466,7 +1466,13 @@
return;
int32 level=GetLevel();
int32 regen = 0;
- if (IsSitting()) { //this should be changed so we dont med while camping, etc...
+
+if(RuleH(Character, ManaRegen) != NULL)
+{ regen = ((Hook_Character_ManaRegen) RuleH(Character, ManaRegen))(IsSitting(), GetLevel(), GetMaxMana(), GetINT(), GetSkill(MEDITATE), itembonuses.ManaRegen, spellbonuses.ManaRegen);
+ medding = IsSitting() && HasSkill(MEDITATE);
+}
+else
+{ if (IsSitting()) { //this should be changed so we dont med while camping, etc...
if(HasSkill(MEDITATE)) {
medding = true;
regen = (((GetSkill(MEDITATE)/10)+(level-(level/4)))/4)+4;
@@ -1480,12 +1486,13 @@
medding = false;
regen = 2+spellbonuses.ManaRegen+itembonuses.ManaRegen+(le vel/5);
}
+}
regen += GetAA(aaMentalClarity);
regen += GetAA(aaBodyAndMindRejuvenation);
regen = (regen * RuleI(Character, ManaRegenMultiplier)) / 100;
SetMana(GetMana() + regen);
Bulle
03-01-2008, 09:18 PM
Index: exp.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/exp.cpp,v
retrieving revision 1.2.2.14
diff -u -b -B -r1.2.2.14 exp.cpp
--- exp.cpp 16 Jan 2007 04:04:53 -0000 1.2.2.14
+++ exp.cpp 2 Mar 2008 09:17:01 -0000
@@ -34,7 +34,6 @@
void Client::AddEXP(int32 add_exp, int8 conlevel, bool resexp) {
if (m_epp.perAA<0 || m_epp.perAA>100)
m_epp.perAA=0; // stop exploit with sanity check
-
int32 add_aaxp;
if(resexp) {
add_aaxp = 0;
@@ -117,6 +116,8 @@
return; // Must be invalid class/race
}
+ if(RuleH(XP, PreChange) != NULL)
+ ((Hook_XP_PreChange) RuleH(XP, PreChange))(this, set_exp, set_aaxp, isrezzexp, m_pp.exp, m_pp.expAA);
if ((set_exp + set_aaxp) > (m_pp.exp+m_pp.expAA)) {
if (isrezzexp)
@@ -312,7 +320,10 @@
uint32 Client::GetEXPForLevel(int16 check_level)
{
- int16 check_levelm1 = check_level-1;
+if(RuleH(Character, EXPForLevel) != NULL)
+ return ((Hook_Character_EXPForLevel) RuleH(Character, EXPForLevel))(check_level, GetBaseRace(), GetClass());
+else
+{ int16 check_levelm1 = check_level-1;
float mod;
if (check_level < 31)
mod = 1.0;
@@ -368,6 +379,7 @@
return(uint32(base * mod));
}
+}
void Group::SplitExp(uint32 exp, Mob* other) {
if( other->CastToNPC()->MerchantType != 0 ) // Ensure NPC isn't a merchant
Bulle
03-01-2008, 09:25 PM
Index: spells.cpp
================================================== =================
RCS file: /cvsroot/eqemulator/EQEmuCVS/Source/zone/spells.cpp,v
retrieving revision 1.14.2.44
diff -u -b -B -r1.14.2.44 spells.cpp
--- spells.cpp 21 Feb 2007 16:04:23 -0000 1.14.2.44
+++ spells.cpp 2 Mar 2008 09:24:51 -0000
@@ -480,7 +480,8 @@
}
bool Client::CheckFizzle(int16 spell_id)
-{
+{ float fizzlechance, diff;
+
// GMs don't fizzle
if (GetGM()) return(true);
@@ -516,7 +517,12 @@
//is there any sort of focus that affects fizzling?
- // neotokyo: this is my try to get something going
+if(RuleH(Spells, FizzleChance) != NULL)
+{ fizzlechance = ((Hook_Spells_FizzleChance) RuleH(Spells, FizzleChance))(this, spells, spell_id);
+ diff = 0;
+}
+else
+{ // neotokyo: this is my try to get something going
int par_skill;
int act_skill;
@@ -556,7 +562,7 @@
// > 0 --> skill is lower, higher chance of fizzle
// < 0 --> skill is better, lower chance of fizzle
// the max that diff can be is +- 235
- float diff = par_skill + spells[spell_id].basediff - act_skill;
+ diff = par_skill + spells[spell_id].basediff - act_skill;
// if you have high int/wis you fizzle less, you fizzle more if you are stupid
if (GetCasterClass() == 'W')
@@ -566,10 +572,11 @@
// base fizzlechance is lets say 5%, we can make it lower for AA skills or whatever
float basefizzle = 10;
- float fizzlechance = basefizzle - specialize + diff / 5.0;
+ fizzlechance = basefizzle - specialize + diff / 5.0;
// always at least 5% chance to fail or succeed
fizzlechance = fizzlechance < 5 ? 5 : (fizzlechance > 95 ? 95 : fizzlechance);
+}
float fizzle_roll = MakeRandomFloat(0, 100);
mlog(SPELLS__CASTING, "Check Fizzle %s spell %d fizzlechance: %0.2f%% diff: %0.2f roll: %0.2f", GetName(), spell_id, fizzlechance, diff, fizzle_roll);
@@ -2717,7 +2724,24 @@
break;
}
- // value in spell to adjust base resist by
+if(RuleH(Spells, FinalResistChance) != NULL)
+{ struct _Hook_Spells_FinalResistChance_Parameters Parameters;
+ Parameters.AttackerIsClient = caster->IsClient();
+ Parameters.AttackerLevel = caster->GetLevel();
+ Parameters.AttackerCha = caster->GetCHA();
+ if(Parameters.AttackerIsClient)
+ Parameters.AttackerMeditation = caster->GetSkill(MEDITATE);
+ Parameters.DefenderIsClient = this->IsClient();
+ Parameters.DefenderLevel = this->GetLevel();
+ Parameters.DefenderResist = resist;
+ Parameters.spells = spells;
+ Parameters.spell_id = spell_id;
+ resistchance = ((Hook_Spells_FinalResistChance) RuleH(Spells, FinalResistChance))(&Parameters);
+ if(caster->IsClient())
+ caster->CastToClient()->CheckIncreaseSkill(MEDITATE);
+}
+else
+{ // value in spell to adjust base resist by
if(spell_id != 0)
resist += spells[spell_id].ResistDiff;
@@ -2743,7 +2767,7 @@
resistchance -= (lvldiff)*0.8;
}
}
-
+}
/*The idea is we come up with 3 ranges of numbers and a roll between 0 and 100
[[[Empty Space above the resistchance line]]] - If the roll lands up here the spell wasn't resisted, the lower the resist chance the larger this range is
[[[Space between resistchance line and full resist chance line]]] - If the roll ends up here then the spell is resisted but only partially, we take the roll in porportion to where it landed in this range to det how
Bulle
03-01-2008, 09:31 PM
I forgot the default_hooks.h file that contains the hook function declaractions. It is required for the changes in the rulexxx files to compile :
#ifndef DEFAULT_RULES_H_
#define DEFAULT_RULES_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
/************************************************** ******************************************/
/* Common hooks. Only hooks implemented in both the world and zone must go in this section. */
/************************************************** ******************************************/
/**
*/
typedef double (*Hook_Random_RandomFloat)(double low, double high);
/************************************************** ***************************************/
/* World-related hooks. Only hooks (and includes) used in World must go to this section. */
/************************************************** ***************************************/
#ifdef WORLD
#include "../common/eq_packet_structs.h"
/** 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)(CharC reate_Struct *cc);
#endif /* WORLD */
/************************************************** ***************************************/
/* Zone-related hooks. Only hooks (and includes) used in Zone must go to this section. */
/************************************************** ***************************************/
#ifdef ZONE
#include "../zone/client.h"
typedef struct _Hook_Attack_ToHitChance_Parameters
{ bool IsPvp;
bool AttackerIsClient;
int8 AttackerLevel;
sint16 AttackerDex;
uint16 AttackerWeaponSkill; /* not applicable if !AttackerIsClient */
uint16 AttackerOffense; /* not applicable if !AttackerIsClient */
bool DefenderIsClient;
int8 DefenderLevel;
sint16 DefenderAgi;
uint16 DefenderDefense; /* not applicable if !DefenderIsClient */
} *Hook_Attack_ToHitChance_Parameters;
/** Called during the function checking whether an attack actualy hits.
* Computes the percentage chance of hitting based on level, weapon skill, DEX and AGI.
* The regular rules will be used for the rest of the hit chance (mods, AAs etc).
* 'Paramters' contains all information available for the computation.
*/
typedef float (*Hook_Attack_ToHitChance)(Hook_Attack_ToHitChance _Parameters Parameters);
typedef struct _Hook_Attack_ClientDamageRange_Parameters
{ sint16 AttackerStr;
int8 AttackerLevel;
int AttackerWeaponDamage;
int AttackerWeaponDelay;
int8 DefenderLevel;
int MinHit; /* output only */
int MaxHit; /* output only */
} *Hook_Attack_ClientDamageRange_Parameters;
/**
* 'Parameters' contains all information available for the computation.
*/
typedef void (*Hook_Attack_ClientDamageRange)(Hook_Attack_Clien tDamageRange_Parameters Parameters);
typedef struct _Hook_Attack_NpcDamageRange_Parameters
{ sint16 AttackerStr;
int8 AttackerLevel;
int AttackerMaxDamage;
int AttackerDelay;
int8 DefenderLevel;
int MinHit; /* output only */
int MaxHit; /* output only */
} *Hook_Attack_NpcDamageRange_Parameters;
/**
* 'Parameters' contains all information available for the computation.
*/
typedef void (*Hook_Attack_NpcDamageRange)(Hook_Attack_NpcDamag eRange_Parameters Parameters);
typedef struct _Hook_Attack_Mitigation_Parameters
{ bool AttackerIsClient;
int8 AttackerLevel;
uint16 AttackerOffense; /* not applicable if !AttackerIsClient */
bool DefenderIsClient;
int8 DefenderLevel;
sint16 DefenderAC;
uint16 DefenderDefense; /* not applicable if !DefenderIsClient */
sint32 Damage; /* input-output */
} *Hook_Attack_Mitigation_Parameters;
/**
* 'Parameters' contains all information available for the computation.
*/
typedef void (*Hook_Attack_Mitigation)(Hook_Attack_Mitigation_P arameters Parameters);
/** Called at the end of the function where the bonuses given by an item are cumulated to the current item bonuses a character receives.
* 'inst' is the item to evaluate.
* 'newbon' is the current set of bonuses received from items by the character.
*/
typedef void (*Hook_Character_PostAddItemBonuses)(const ItemInst *inst, StatBonuses* newbon);
/**
*/
typedef sint16 (*Hook_Character_CalcAC)(uint8 Level, uint16 Defense, sint16 ItemsAC, sint16 SpellsAC);
/**
*/
typedef uint32 (*Hook_Character_EXPForLevel)(uint8 Level, int16 CharacterRace, int8 CharacterClass);
/**
*/
typedef int32 (*Hook_Character_ManaRegen)(bool IsSitting, uint8 Level, sint32 MaxMana, sint16 Int, uint16 MeditateSkill, sint32 ItemsManaRegen, sint32 SpellsManaRegen);
/**
*/
typedef sint16 (*Hook_Character_ChanceOfSkillIncrease)(SkillType skillid, int CurrentSkill, int MaxSkill, int chancemodi);
/** Called right before the fizzle roll is made, to compute the percentage of chance to fizzle.
* 'ThisClient' is the character casting the spell.
* 'spell_id' is the ID of the spell being cast.
* The check whether the caster can never fizzle due to AAs has already been done.
*/
typedef float (*Hook_Spells_FizzleChance)(Client *ThisClient, const SPDat_Spell_Struct *spells, int16 spell_id);
typedef struct _Hook_Spells_FinalResistChance_Parameters
{ bool AttackerIsClient;
int8 AttackerLevel;
sint16 AttackerCha;
uint16 AttackerMeditation; /* not applicable if !AttackerIsClient */
bool DefenderIsClient;
int8 DefenderLevel;
sint16 DefenderResist;
const SPDat_Spell_Struct *spells;
int16 spell_id;
} *Hook_Spells_FinalResistChance_Parameters;
/**
*/
typedef float (*Hook_Spells_FinalResistChance)(Hook_Spells_Final ResistChance_Parameters Parameters);
/** Called during exprience change, before the new XP values are set. Ideal place to show a custom message with the numeric XP values.
* The XP for the current client is going to be set to 'set_exp' and 'set_aaxp'.
* The client currently has 'orig_exp' XP and 'orig_aaxp'.
* 'isrezzexp' indicates whether this is XP regained after a rez.
* The hook cannot affect the XP change in any way. The XP will be changed normally after the hook returns.
*/
typedef void (*Hook_XP_PreChange)(Client *ThisClient, int32 set_exp, int32 set_aaxp, bool isrezzexp, int32 orig_exp, int32 orig_aaxp);
#endif /* ZONE */
#endif /*DEFAULT_RULES_H_*/
Bulle
03-01-2008, 09:35 PM
What has been explained since the beginning of this post is still still somewhat applicable, with some changes. To implement one (or more) hooks you need to :
- create a shared library with the hook functions implementation. You want to separate "world" and "zone", so it means two shared libraries (in my case hero_world and hero_zone).
- add an entry in the rule_values database for each hook to activate
Bulle
03-01-2008, 09:39 PM
Here is my code for the hero_world.dll library (hero_world_Hooks.cpp) :
#include "../common/debug.h"
#include "../common/default_Hooks.h"
#include "../common/classes.h"
#include "../common/races.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_world_CharacterCreation_ChangeCreationInfo(Ch arCreate_Struct *cc)
{ cc->class_ = RANGER;
/* Adjust the stats to provide a similar amount of points per race.
* Good or neutral races get 535 points total, evil races 555.
*/
switch(cc->race)
{ case BARBARIAN: break;
case DARK_ELF: cc->CHA += 23; break;
case DWARF: cc->CHA += 7; break;
case ERUDITE: cc->CHA += 5; break;
case FROGLOK: cc->CHA -= 15; break;
case GNOME: cc->CHA += 10; break;
case HALF_ELF: cc->CHA += 10; break;
case HALFLING: cc->CHA += 3; break;
case HIGH_ELF: cc->AGI -= 7; break;
case HUMAN: cc->CHA += 10; break;
case IKSAR: break;
case OGRE: cc->CHA += 1; break;
case TROLL: cc->DEX += 10; cc->STA += 18; break;
case VAHSHIR: cc->DEX += 10; break;
case WOOD_ELF: break;
default: printf("unknown race %d\n", cc->race);
}
}
CheckHookSignature(CharacterCreation, ChangeCreationInfo, hero_world_CharacterCreation_ChangeCreationInfo);
Enlarge one column in the database to allow specifying looong values :
ALTER TABLE rule_values MODIFY rule_value VARCHAR(256);
The hook is activated with the following SQL query in the database :
INSERT INTO rule_values VALUES ('1','(world) CharacterCreation:ChangeCreationInfo','hero_world: hero_world_CharacterCreation_ChangeCreationInfo');
The entry name is of the form "(world) <hook>" for world hooks. The value is the name of the shared library (no extension, for Unix compatibility) then the name of the function implementing the hook.
Bulle
03-01-2008, 09:44 PM
There are many more zone hooks. They are implemented in hero_zone_Hooks.cpp, which generates the hero_world.dll shared library.
I removed a couple hooks of mine as they rely on other non-hook related changes on my server.
Those custom hooks are of course only there as examples. You do not want them on your own server :)
#include "../common/debug.h"
#include "../common/default_Hooks.h"
#include "../common/MiscFunctions.h"
#include <math.h>
#ifdef WIN32
#define exportfunc extern "C" __declspec(dllexport)
#else
#define exportfunc extern "C"
#endif
#define SkillRegulationMod (+20)
#define StatRegulationMod (-50)
#define ZeroIfNegative(x) ((x) < 0 ? 0 : (x))
/** The to-hit chance is computed as :
* 100 - 100 * (DEF AGI + StatRegulationMod) / ( (DEF AGI + StatRegulationMod) + (ATK DEX + StatRegulationMod) + (ATK Skill + SkillRegulationMod) )
* If one of the opponent is not a client then its skill is equal to (Level + 1) * 5.
*/
exportfunc float hero_zone_Attack_ToHitChance(Hook_Attack_ToHitChan ce_Parameters Parameters)
{ float Result;
uint16 AttackerWeaponSkill;
//printf("Bulle-ToHit-Hook-01 : PVP=%d / ATK CLT=%d / ATK LVL=%d / ATK DEX=%d / ATK WEA=%d / ATK OFF=%d\n", Parameters->IsPvp, Parameters->AttackerIsClient, Parameters->AttackerLevel, Parameters->AttackerDex, Parameters->AttackerIsClient?Parameters->AttackerWeaponSkill : -1, Parameters->AttackerIsClient?Parameters->AttackerOffense : -1);
//printf("Bulle-ToHit-Hook-02 : PVP=%d / DEF CLT=%d / DEF LVL=%d / DEF AGI=%d / DEF OFF=%d\n", Parameters->IsPvp, Parameters->DefenderIsClient, Parameters->DefenderLevel, Parameters->DefenderAgi, Parameters->AttackerIsClient?Parameters->DefenderDefense : -1);
if(Parameters->AttackerIsClient)
AttackerWeaponSkill = Parameters->AttackerWeaponSkill;
else
AttackerWeaponSkill = (Parameters->AttackerLevel + 1) * 5;
Result = 100.0 - 100.0 * ZeroIfNegative(Parameters->DefenderAgi + StatRegulationMod) /
( ZeroIfNegative(Parameters->DefenderAgi + StatRegulationMod)
+ ZeroIfNegative(Parameters->AttackerDex + StatRegulationMod)
+ ZeroIfNegative(AttackerWeaponSkill + SkillRegulationMod) / 2
+ 1 /* avoids division by zero */
);
printf("Bulle-ToHit-Hook-03 : to hit=%f\n", Result);
return Result;
}
CheckHookSignature(Attack, ToHitChance, hero_zone_Attack_ToHitChance);
/** The damage range is computed as :
* MinHit = (ATK STR + StatRegulationMod) * ATK WEAP DLY / 100
* MaxHit = MinHit + 3 * ATK WEAP DMG
*/
exportfunc void hero_zone_Attack_ClientDamageRange(Hook_Attack_Cli entDamageRange_Parameters Parameters)
{
//printf("Bulle-ClientDamageRange-Hook-01 : ATK STR=%d / ATK LVL=%d / ATK DLY=%d / ATK DMG=%d / DEF LVL=%d\n", Parameters->AttackerStr, Parameters->AttackerLevel, Parameters->AttackerWeaponDelay, Parameters->AttackerWeaponDamage, Parameters->DefenderLevel);
Parameters->MinHit = (int) (ZeroIfNegative(Parameters->AttackerStr + StatRegulationMod) * Parameters->AttackerWeaponDelay / 100.0);
Parameters->MaxHit = Parameters->MinHit + 3 * Parameters->AttackerWeaponDamage;
printf("Bulle-ClientDamageRange-Hook-02 : min_hit=%d max_hit=%d\n", Parameters->MinHit, Parameters->MaxHit);
}
CheckHookSignature(Attack, ClientDamageRange, hero_zone_Attack_ClientDamageRange);
/** The damage range is computed as :
* MinHit = (ATK STR + StatRegulationMod) * ATK DLY / 100
* MaxHit = MinHit + 2 * ATK MAXDMG
*/
exportfunc void hero_zone_Attack_NpcDamageRange(Hook_Attack_NpcDam ageRange_Parameters Parameters)
{
//printf("Bulle-NpcDamageRange-Hook-01 : ATK STR=%d / ATK LVL=%d / ATK DLY=%d / ATK MAXDMG=%d / DEF LVL=%d\n", Parameters->AttackerStr, Parameters->AttackerLevel, Parameters->AttackerDelay, Parameters->AttackerMaxDamage, Parameters->DefenderLevel);
Parameters->MinHit = (int) (ZeroIfNegative(Parameters->AttackerStr + StatRegulationMod) * Parameters->AttackerDelay / 100.0);
Parameters->MaxHit = Parameters->MinHit + 2 * Parameters->AttackerMaxDamage;
printf("Bulle-NpcDamageRange-Hook-02 : min_hit=%d max_hit=%d\n", Parameters->MinHit, Parameters->MaxHit);
}
CheckHookSignature(Attack, NpcDamageRange, hero_zone_Attack_NpcDamageRange);
exportfunc void hero_zone_Attack_Mitigation(Hook_Attack_Mitigation _Parameters Parameters)
{ sint32 InitialDamage = Parameters->Damage;
float Mitigation;
uint16 AttackerOffense, DefenderDefense;
double AdjustedDefenderAC;
//printf("Bulle-Mitigation-Hook-01 : ATK CLT=%d / ATK LVL=%d / ATK OFF=%d / DEF CLT=%d / DEF LVL=%d / DEF AC=%d / DEF DEF=%d\n", Parameters->AttackerIsClient, Parameters->AttackerLevel, Parameters->AttackerIsClient ? Parameters->AttackerOffense : -1, Parameters->DefenderIsClient, Parameters->DefenderLevel, Parameters->DefenderAC, Parameters->DefenderIsClient ? Parameters->DefenderDefense : -1);
if(Parameters->AttackerIsClient)
AttackerOffense = Parameters->AttackerOffense;
else
AttackerOffense = (Parameters->AttackerLevel + 1) * 5;
if(Parameters->DefenderIsClient)
{ DefenderDefense = Parameters->DefenderDefense;
AdjustedDefenderAC = Parameters->DefenderAC;
}
else
{ DefenderDefense = (Parameters->DefenderLevel + 1 )* 5;
AdjustedDefenderAC = pow(Parameters->DefenderAC * 1.0, 0.9);
}
Mitigation = (AdjustedDefenderAC + ZeroIfNegative(DefenderDefense + SkillRegulationMod) * 1.25) /
(AdjustedDefenderAC + ZeroIfNegative(DefenderDefense + SkillRegulationMod) * 1.25 + ZeroIfNegative(AttackerOffense + SkillRegulationMod) * 5);
Parameters->Damage = (int) ((1.0 - Mitigation) * InitialDamage);
printf("Bulle-Mitigation-Hook-02 : dmgbfr=%d mitig=%f dmgaft=%d\n", InitialDamage, Mitigation, Parameters->Damage);
}
CheckHookSignature(Attack, Mitigation, hero_zone_Attack_Mitigation);
exportfunc sint16 hero_zone_Character_CalcAC(uint8 Level, uint16 Defense, sint16 ItemsAC, sint16 SpellsAC)
{ sint16 Result = ItemsAC + SpellsAC;
return Result;
}
CheckHookSignature(Character, CalcAC, hero_zone_Character_CalcAC);
exportfunc uint32 hero_zone_Character_EXPForLevel(uint8 Level, int16 CharacterRace, int8 CharacterClass)
{ uint32 Result = 100.0 * pow(1.2, Level + 10);
printf("Bulle-EXPForLevel-Hook-01 : level=%d XP=%d\n", Level, Result);
return Result;
}
CheckHookSignature(Character, EXPForLevel, hero_zone_Character_EXPForLevel);
exportfunc int32 hero_zone_Character_ManaRegen(bool IsSitting, uint8 Level, sint32 MaxMana, sint16 Int, uint16 MeditateSkill, sint32 ItemsManaRegen, sint32 SpellsManaRegen)
{ sint16 Result;
Result = MaxMana * Int / 4000;
if(!IsSitting)
Result = Result / 10;
Result += 2 + ItemsManaRegen + SpellsManaRegen;
//printf("Bulle-ManaRegen-Hook-01 : regen=%d\n", Result);
return Result;
}
CheckHookSignature(Character, ManaRegen, hero_zone_Character_ManaRegen);
/** Base chance is a linear function such that base chance is 25% when skill is 0, and 0% when skill is 250.
* The final chance is the base chance plus the chance modifier, or 5%, whichever is greater.
*/
exportfunc sint16 hero_zone_Character_ChanceOfSkillIncrease(SkillTyp e skillid, int CurrentSkill, int MaxSkill, int chancemodi)
{ sint16 Result;
Result = (25 * (250 - CurrentSkill) + 1250) / 300 + chancemodi;
if(Result < 5)
Result = 5;
printf("Bulle-ChanceOfSkillIncrease-Hook-01 : skill=%d chance=%d\n", CurrentSkill, Result);
return Result;
}
CheckHookSignature(Character, ChanceOfSkillIncrease, hero_zone_Character_ChanceOfSkillIncrease);
exportfunc float hero_zone_Spells_FinalResistChance(Hook_Spells_Fin alResistChance_Parameters Parameters)
{ float Result;
uint16 AttackerMeditation;
if(Parameters->AttackerIsClient)
AttackerMeditation = Parameters->AttackerMeditation;
else
AttackerMeditation = (Parameters->AttackerLevel + 1) * 5;
Result = 100.0 * Parameters->DefenderResist /
(1 + ZeroIfNegative(1.0 * Parameters->DefenderResist + (Parameters->AttackerCha + StatRegulationMod)
+ (AttackerMeditation + SkillRegulationMod) - Parameters->spells[Parameters->spell_id].ResistDiff));
printf("Bulle-Resist-Hook-01 : resist=%f\n", Result);
return Result;
}
CheckHookSignature(Spells, FinalResistChance, hero_zone_Spells_FinalResistChance);
/** Displays numeric XP value gains (or loss).
*/
exportfunc void hero_zone_XP_PreChange(Client *ThisClient, int32 set_exp, int32 set_aaxp, bool isrezzexp, int32 orig_exp, int32 orig_aaxp)
{ if(set_exp - orig_exp > 0)
ThisClient->Message(15, "You have gained %d XP.", set_exp - orig_exp);
else if(set_exp - orig_exp < 0)
ThisClient->Message(15, "You have lost %d XP.", set_exp - orig_exp);
if(set_aaxp - orig_aaxp > 0)
ThisClient->Message(15, "You have gained %d AA XP.", set_aaxp - orig_aaxp);
else if(set_aaxp - orig_aaxp < 0)
ThisClient->Message(15, "You have lost %d AA XP.", set_aaxp - orig_aaxp);
//printf("Bulle-XPPreChange-Hook-01 : XP:%d->%d AA:%d->%d\n", orig_exp, set_exp, orig_aaxp, set_aaxp);
}
CheckHookSignature(XP, PreChange, hero_zone_XP_PreChange);
Bulle
03-01-2008, 09:44 PM
INSERT INTO rule_values VALUES ('1','(zone) Attack:ToHitChance','hero_zone:hero_zone_Attack_To HitChance');
INSERT INTO rule_values VALUES ('1','(zone) Attack:ClientDamageRange','hero_zone:hero_zone_Att ack_ClientDamageRange');
INSERT INTO rule_values VALUES ('1','(zone) Attack:NpcDamageRange','hero_zone:hero_zone_Attack _NpcDamageRange');
INSERT INTO rule_values VALUES ('1','(zone) Attack:Mitigation','hero_zone:hero_zone_Attack_Mit igation');
INSERT INTO rule_values VALUES ('1','(zone) Character:CalcAC','hero_zone:hero_zone_Character_C alcAC');
INSERT INTO rule_values VALUES ('1','(zone) Character:EXPForLevel','hero_zone:hero_zone_Charac ter_EXPForLevel');
INSERT INTO rule_values VALUES ('1','(zone) Character:ManaRegen','hero_zone:hero_zone_Characte r_ManaRegen');
INSERT INTO rule_values VALUES ('1','(zone) Character:ChanceOfSkillIncrease','hero_zone:hero_z one_Character_ChanceOfSkillIncrease');
INSERT INTO rule_values VALUES ('1','(zone) XP:PreChange','hero_zone:hero_zone_XP_PreChange');
INSERT INTO rule_values VALUES ('1','(zone) Spells:FinalResistChance','hero_zone:hero_zone_Spe lls_FinalResistChance');
Bulle
03-01-2008, 09:48 PM
Now you can probably understand why I was so quiet about the topic :)
This code submission is quite huge, I will understand if noone can find the time to try it out (especially as I may have introduced errors when porting the code back to CVS from my SVN repository, and weeding out the other unnecessary change I made).
If I make other interesting changes to the server I will make sure to post them somewhere else. Now after all this effort it is Zebu server time ;-)
Bulle
vBulletin® v3.8.11, Copyright ©2000-2025, vBulletin Solutions Inc.