-
Notifications
You must be signed in to change notification settings - Fork 580
Improve documentation for c++ hooks #444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
4d5426c
Update Create-a-Module.md
Yehonal d0fc97c
Update Hooks-Script.md
Yehonal 723fce4
Update Hooks-Script.md
Yehonal ef7c62b
Update Hooks-Script.md
Yehonal 14790c6
Update Hooks-Script.md
Yehonal c2eb516
Update Hooks-Script.md
Yehonal b4e15f4
Update Hooks-Script.md
Yehonal cf27791
Update Hooks-Script.md
Yehonal c9d8c00
Update Create-a-Module.md
Yehonal 5f0222c
Update Create-a-Module.md
Yehonal f3bbcb9
Rename Hooks-Script.md to hooks-script.md
Yehonal eacf091
Update hooks-script.md
Yehonal 14ab5bc
Update hooks-script.md
Yehonal 01884dc
Update docs/Create-a-Module.md
Yehonal a5fb2c7
Update hooks-script.md
Yehonal b6eb106
Update hooks-script.md
Yehonal cda0656
Update docs/hooks-script.md
Yehonal 25b2561
Apply suggestions from code review
Yehonal 7c93c41
Update hooks-script.md
Yehonal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| # The ScriptAI system | ||
|
|
||
| The ScriptAI system implemented by AC uses a special [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) strategy to implement an event-driven programming which is also the **CORE** of our modular system. | ||
|
|
||
| This guide, together with our [module system](Create-a-Module.md) lets you extend the AzerothCore without patching it directly. This allows you to update your repository by keeping your additions and customizations conflict-free! | ||
|
|
||
| # Resources | ||
|
|
||
| ## Hook List | ||
|
|
||
| The list of the hooks can be found inside the [ScriptMgr.h file](https://github.com/azerothcore/azerothcore-wotlk/blob/master/src/server/game/Scripting/ScriptMgr.h) | ||
|
|
||
| ## Glossary | ||
|
|
||
| * **Hook**: A function that is declared inside a **_ScriptObject_** and that calls the **_Listeners_** | ||
| * **ScriptObject**: Abstract class that should be extended to create the **_Observer_**. | ||
| * **Script type**: The class that extends the `ScriptObject` (e.g. `PLayerScript`, `CreatureScript`, etc.), | ||
| when you extend the script type class you are initializing a **_Concrete Observer_** | ||
| * **ScriptRegistry**: This class contains the registry of all the registered Observers. | ||
| * **ScriptMgr**: The singleton class that contains the list of all the available hooks and acts as a **_Observable_** by notifying the **_Listeners_** when an event is dispatched. | ||
|
|
||
| ## How to create a hook | ||
|
|
||
| Before going through the next step you should ask yourself: do I have to create a new script type based on `ScriptObject` class or can I reuse one of those already existing? | ||
|
|
||
| A script type is normally strictly related to a certain class of the core. For example: | ||
| - `PlayerScript` -> `Player` class | ||
| - `WorldScript -> `World` class | ||
| - `CreatureScript` -> `Creature` class | ||
| and so on. | ||
|
|
||
| There are some exceptions such as the `GlobalScript` which is an Observer used in different classes throughout the core. But generally speaking, a script type should refer to a specific class. | ||
|
|
||
| Therefore, if you create a new class inside that has to be extended with hooks, then you can proceed with the first point. | ||
|
|
||
| However, most of the time you just have to add new hooks to existing scripts, in this case just jump to point 2 of this chapter. | ||
|
|
||
| ### 1) Standard procedure when adding new script type classes | ||
|
|
||
| First of all, define the actual class, and have it inherit from ScriptObject, like so: | ||
|
|
||
| ```C++ | ||
| class MyScriptType : public ScriptObject | ||
| { | ||
| uint32 _someId; | ||
| private: | ||
| void RegisterSelf(); | ||
| protected: | ||
| MyScriptType(const char* name, uint32 someId) | ||
| : ScriptObject(name), _someId(someId) | ||
| { | ||
| ScriptRegistry<MyScriptType>::AddScript(this); | ||
| } | ||
| public: | ||
| // If a virtual function in your script type class is not necessarily | ||
| // required to be overridden, just declare it virtual with an empty | ||
| // body. If, on the other hand, it's logical only to override it (i.e. | ||
| // if it's the only method in the class), make it pure virtual, by adding | ||
| // = 0 to it. | ||
| virtual void OnBeforeSomeEvent(uint32 /*someArg1*/, std::string& /*someArg2/*) { } | ||
| // This is a pure virtual function: | ||
| virtual void OnAnotherEvent(uint32 /*someArg*/) = 0; | ||
| } | ||
| ``` | ||
|
|
||
| Next, you need to add a specialization for ScriptRegistry. Put this at the beginning of ScriptMgr.cpp: | ||
| ```C++ | ||
| template class ScriptRegistry<MyScriptType>; | ||
| ``` | ||
|
|
||
| Now add the register at the bottom of the ScriptMgr.cpp: | ||
|
|
||
| ```C++ | ||
| MyScriptType::MyScriptType(const char* name) | ||
| : ScriptObject(name) | ||
| { | ||
| ScriptRegistry<MyScriptType>::AddScript(this); | ||
| } | ||
| ``` | ||
|
|
||
| Then add a cleanup routine in `ScriptMgr::unload()` | ||
|
|
||
| ``` | ||
| SCR_CLEAR(MyScriptType); | ||
| ``` | ||
|
|
||
| And finally your class is good to go with the script system! | ||
|
|
||
| ### 2) Implement the hooks functions | ||
|
|
||
| If you didn't follow point 1 and you want to reuse an existing ScriptObject, then you have to declare the functions | ||
| within one of the pre-existing ScriptObject classes first (such as PlayerScript, ServerScript etc.) | ||
|
|
||
| #### Declare your hooks | ||
| What you need to do now is add functions to ScriptMgr that can be called from the core to actually trigger | ||
| certain events. | ||
|
|
||
| In ScriptMgr.h , inside the `class ScriptMgr` | ||
|
|
||
| ```C++ | ||
| void OnBeforeSomeEvent(uint32 someArg1, std::string& someArg2); | ||
| void OnAnotherEvent(uint32 someArg); | ||
| ``` | ||
|
|
||
| NOTE: for certain scripts the method declared inside the ScriptMgr class and the one declared into the related ScriptObject, | ||
| don't always match. For instance: `OnLogin` is a hook from the PlayerScript that is declared as `OnPlayerLogin` when | ||
| used inside the ScriptMgr class, thus avoid collisions with other methods since the ScriptMgr class collects hooks from all | ||
| the ScriptObjects within the same list. | ||
|
|
||
| #### Define your hooks | ||
|
|
||
| This step defines the way your hook should call the registered listeners. | ||
| The most common way to do it is the following | ||
|
|
||
| In ScriptMgr.cpp: | ||
|
|
||
| ```C++ | ||
| void ScriptMgr::OnBeforeSomeEvent(uint32 someArg1, std::string& someArg2) | ||
| { | ||
| FOREACH_SCRIPT(MyScriptType)->OnBeforeSomeEvent(someArg1, someArg2); | ||
| } | ||
| void ScriptMgr::OnAnotherEvent(uint32 someArg) | ||
| { | ||
| FOREACH_SCRIPT(MyScriptType)->OnAnotherEvent(someArg); | ||
| } | ||
| ``` | ||
|
|
||
| Now you simply call these two functions from anywhere in the core to trigger the | ||
| event on all registered scripts of that type. | ||
|
|
||
| ### How to call your hooks | ||
|
|
||
| The ScriptMgr class is initialized within the AC as a singleton that will contain all the observers (ScriptObjects) and their related registered listeners (hooks). | ||
| AC provides a global property called "sScriptMgr" that you can use to call your script within the AC functions. | ||
|
|
||
| For instance: | ||
|
|
||
| ```C++ | ||
| void CoreClass::SomeEvent() | ||
| { | ||
| uint32 arg1=10; | ||
| std::string arg2="something"; | ||
|
|
||
| sScriptMgr->OnBeforeSomeEvent(arg1, arg2); | ||
|
|
||
| //[...] | ||
| } | ||
| ``` | ||
|
|
||
| ## Documenting your hook | ||
|
|
||
| Remember to document your new hook by following the [How to document your code](how-to-document-code.md) guide. | ||
|
|
||
| When you create a new hook to publish into the AC repo, one of the acceptance criteria is to write proper documentation for it, | ||
| hence other people know how to use it properly. So please, read that guide carefully. | ||
|
|
||
| ## Naming conventions | ||
|
|
||
| Every hook must have the following naming convention: | ||
|
|
||
| `On[When]<Action>` | ||
|
|
||
| For example: | ||
|
|
||
| * `OnBeforeConfigLoad` | ||
| * `OnAfterArenaRatingCalculation` | ||
|
|
||
| The action normally matches the name of the function within which the hook is called. | ||
| If the parent function is complex enough to even host different hooks, then the action | ||
| should reflect what the hook is used for. | ||
|
|
||
| The `[When]` part is optional, but strongly suggested. | ||
| It helps to understand in which part of the parent function the hook is called. | ||
| For instance, you can have both `OnBeforeConfigLoad` and `OnAfterConfigLoad`, | ||
| to change the behaviour before and after the config is loaded. | ||
|
|
||
| ## Advanced hooks | ||
|
|
||
| ### How to change the behaviour of a function (filtering) | ||
|
|
||
| With hooks you can't only run specific actions at a specific time, you can even change the behaviour of the script where the hook is called | ||
| To do so, you have 2 solutions: | ||
|
|
||
| #### 1) Using reference parameters | ||
|
|
||
| This is the most common one. Basically using the concept of passing a parameter by reference you can change everything that is passed to the hook itself. | ||
| For instance: | ||
|
|
||
| ```C++ | ||
| OnMotdChange(std::string& newMotd) | ||
| ``` | ||
|
|
||
| Passing the newMotd with the '&' character you allow the listeners to change the value of the Motd when that action is called. | ||
|
|
||
| #### 2) Using a bool return value | ||
|
|
||
| This approach is not very common, most of the hooks return a "void" type, and working with references is easier most of the time, but if you really need it you can implement a hook that is declared in this way: | ||
|
|
||
| ```C++ | ||
| bool ScriptMgr::OnBeforePlayerTeleport(Player* player, uint32 mapid, float x, float y, float z, float orientation, uint32 options, Unit* target) | ||
| { | ||
| bool ret = true; | ||
| FOR_SCRIPTS_RET(PlayerScript, itr, end, ret) // return true by default if not scripts | ||
| if (!itr->second->OnBeforeTeleport(player, mapid, x, y, z, orientation, options, target)) | ||
| ret = false; // we change ret value only when scripts return false | ||
|
|
||
| return ret; | ||
| } | ||
| ``` | ||
|
|
||
| This hook notifies all the listeners but also catches when at least one of the registered listener returns "false", in that case the final return value will be false as well. | ||
| In this particular case, this hook is used within an if-condition to disallow a player to be teleported if one of the listeners returns **false** for some reason. | ||
|
|
||
| You can implement your different logic (e.g. false by default, true if any) just remember to document it properly! | ||
|
|
||
| ### Create your hook system within your module | ||
|
|
||
| By using the guide above you can even create your ScriptObject within your module to allow people to extend it. | ||
| Some modules, such as the auto-balance, allows customizing certain part of their function by using internal hooks | ||
|
|
||
| You can take a look at this file as an example: https://github.com/azerothcore/mod-autobalance/blob/master/src/AutoBalance.h | ||
|
|
||
| NOTE: You also need to create your own ScriptMgr implementation and offer a singleton to allow calling your hooks. | ||
|
|
||
|
|
||
| ### Final considerations | ||
|
|
||
| There are different other features of the ScriptAI system that have not been included in this documentation, such as the creation of scripts bound | ||
| to specific entities inside our database (E.g. CreatureScript). This advanced usage can be implemented by replicate the related code we have inside the ScriptMgr files. | ||
| If you need any help or you want to improve this documentation, feel free to ask for support and edit this page. | ||
|
|
||
| ## External resources | ||
|
|
||
| - [Stack overflow topic: Is it possible to turn a core patch into a module for AzerothCore?](https://stackoverflow.com/questions/66340549/is-it-possible-to-turn-a-core-patch-into-a-module-for-azerothcore/66340683#66340683) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.