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, 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
  #2  
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
  #3  
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
  #4  
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
  #5  
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
  #6  
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
  #7  
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
Reply

Thread Tools
Display Modes

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 10:37 PM.


 

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