diff options
Diffstat (limited to '')
-rw-r--r-- | MCServer/Plugins/APIDump/APIDesc.lua | 648 | ||||
-rw-r--r-- | MCServer/Plugins/APIDump/WebWorldThreads.html | 116 | ||||
-rw-r--r-- | MCServer/Plugins/APIDump/main.css | 30 | ||||
-rw-r--r-- | MCServer/Plugins/APIDump/main.lua | 350 |
4 files changed, 970 insertions, 174 deletions
diff --git a/MCServer/Plugins/APIDump/APIDesc.lua b/MCServer/Plugins/APIDump/APIDesc.lua index b4b2e11a3..2ea51dbf2 100644 --- a/MCServer/Plugins/APIDump/APIDesc.lua +++ b/MCServer/Plugins/APIDump/APIDesc.lua @@ -364,8 +364,8 @@ g_APIDesc = }, Constants = { - Color = { Notes = "The first character of the color-code-sequence, §" }, - Delimiter = { Notes = "The first character of the color-code-sequence, §" }, + Color = { Notes = "The first character of the color-code-sequence, §" }, + Delimiter = { Notes = "The first character of the color-code-sequence, §" }, Random = { Notes = "Random letters and symbols animate instead of the text" }, Plain = { Notes = "Resets all formatting to normal" }, }, @@ -2029,14 +2029,14 @@ World:ForEachEntity( GetTime = {Return = "number", Notes = "Returns the current OS time, as a unix time stamp (number of seconds since Jan 1, 1970)"}, IsValidBlock = {Params = "BlockType", Return = "bool", Notes = "Returns true if BlockType is a known block type"}, IsValidItem = {Params = "ItemType", Return = "bool", Notes = "Returns true if ItemType is a known item type"}, - ItemToFullString = {Params = "{{cItem|cItem}}", Return = "string", Notes = "Returns the string representation of the item, in the format “ItemTypeText:ItemDamage * Count”"}, + ItemToFullString = {Params = "{{cItem|cItem}}", Return = "string", Notes = "Returns the string representation of the item, in the format “ItemTypeText:ItemDamage * Count”"}, ItemToString = {Params = "{{cItem|cItem}}", Return = "string", Notes = "Returns the string representation of the item type"}, ItemTypeToString = {Params = "ItemType", Return = "string", Notes = "Returns the string representation of ItemType "}, - LOG = {Params = "string", Notes = "Logs a text into the server console using “normal” severity (gray text) "}, - LOGERROR = {Params = "string", Notes = "Logs a text into the server console using “error” severity (black text on red background)"}, - LOGINFO = {Params = "string", Notes = "Logs a text into the server console using “info” severity (yellow text)"}, - LOGWARN = {Params = "string", Notes = "Logs a text into the server console using “warning” severity (red text); OBSOLETE"}, - LOGWARNING = {Params = "string", Notes = "Logs a text into the server console using “warning” severity (red text)"}, + LOG = {Params = "string", Notes = "Logs a text into the server console using “normal” severity (gray text) "}, + LOGERROR = {Params = "string", Notes = "Logs a text into the server console using “error” severity (black text on red background)"}, + LOGINFO = {Params = "string", Notes = "Logs a text into the server console using “info” severity (yellow text)"}, + LOGWARN = {Params = "string", Notes = "Logs a text into the server console using “warning” severity (red text); OBSOLETE"}, + LOGWARNING = {Params = "string", Notes = "Logs a text into the server console using “warning” severity (red text)"}, NoCaseCompare = {Params = "string, string", Return = "number", Notes = "Case-insensitive string comparison; returns 0 if the strings are the same"}, ReplaceString = {Params = "full-string, to-be-replaced-string, to-replace-string", Notes = "Replaces *each* occurence of to-be-replaced-string in full-string with to-replace-string"}, StringSplit = {Params = "string, Seperator", Return = "list", Notes = "Seperates string into multiple by splitting every time Seperator is encountered."}, @@ -2864,6 +2864,638 @@ end; ]], }, -- HOOK_PLAYER_PLACING_BLOCK + HOOK_PLAYER_RIGHT_CLICK = + { + CalledWhen = "A right-click packet is received from the client. Plugin may override / refuse.", + DefaultFnName = "OnPlayerRightClick", -- also used as pagename + Desc = [[ + This hook is called when MCServer receives a right-click packet from the {{cClientHandle|client}}. It + is called before any processing whatsoever is performed on the packet, meaning that hacked / + malicious clients may be trigerring this event very often and with unchecked parameters. Therefore + plugin authors are advised to use extreme caution with this callback.</p> + <p> + Plugins may refuse the default processing for the packet, causing MCServer to behave as if the + packet has never arrived. This may, however, create inconsistencies in the client - the client may + think that they placed a block, while the server didn't process the placing, etc. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player whose client sent the packet" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the block" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the block" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the block" }, + { Name = "BlockFace", Type = "number", Notes = "Face of the block upon which the player interacted. One of the BLOCK_FACE_ constants" }, + }, + Returns = [[ + If the function returns false or no value, MCServer calls other plugins' callbacks and finally sends + the packet for further processing.</p> + <p> + If the function returns true, no other plugins are called, processing is halted. + ]], + }, -- HOOK_PLAYER_RIGHT_CLICK + + HOOK_PLAYER_RIGHT_CLICKING_ENTITY = + { + CalledWhen = "A player has right-clicked an entity. Plugins may override / refuse.", + DefaultFnName = "OnPlayerRightClickingEntity", -- also used as pagename + Desc = [[ + This hook is called when the {{cPlayer|player}} right-clicks an {{cEntity|entity}}. Plugins may + override the default behavior or even cancel the default processing. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has right-clicked the entity" }, + { Name = "Entity", Type = "{{cEntity}} descendant", Notes = "The entity that has been right-clicked" }, + }, + Returns = [[ + If the functino returns false or no value, MCServer calls other plugins' callbacks and finally does + the default processing for the right-click. If the function returns true, no other callbacks are + called and the default processing is skipped. + ]], + }, -- HOOK_PLAYER_RIGHT_CLICKING_ENTITY + + HOOK_PLAYER_SHOOTING = + { + CalledWhen = "When the player releases the bow, shooting an arrow (other projectiles: unknown)", + DefaultFnName = "OnPlayerShooting", -- also used as pagename + Desc = [[ + This hook is called when the {{cPlayer|player}} shoots their bow. It is called for the actual + release of the {{cArrowEntity|arrow}}. FIXME: It is currently unknown whether other + {{cProjectileEntity|projectiles}} (snowballs, eggs) trigger this hook.</p> + <p> + To get the player's position and direction, use the {{cPlayer}}:GetEyePosition() and + cPlayer:GetLookVector() functions. Note that for shooting a bow, the position for the arrow creation + is not at the eye pos, some adjustments are required. FIXME: Export the {{cPlayer}} function for + this adjustment. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player shooting" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called, and finally + MCServer creates the projectile. If the functino returns true, no other callback is called and no + projectile is created. + ]], + }, -- HOOK_PLAYER_SHOOTING + + HOOK_PLAYER_SPAWNED = + { + CalledWhen = "After a player (re)spawns in the world to which they belong to.", + DefaultFnName = "OnPlayerSpawned", -- also used as pagename + Desc = [[ + This hook is called after a {{cPlayer|player}} has spawned in the world. It is called after + {{OnLogin|HOOK_LOGIN}} and {{OnPlayerJoined|HOOK_PLAYER_JOINED}}, after the player name has been + authenticated, the initial worldtime, inventory and health have been sent to the player and the + player spawn packet has been broadcast to all players near enough to the player spawn place. This is + a notification-only event, plugins wishing to refuse player's entry should kick the player using the + {{cPlayer}}:Kick() function.</p> + <p> + This hook is also called when the player respawns after death (and a respawn packet is received from + the client, meaning the player has already clicked the Respawn button). + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has (re)spawned" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called for this event. There is no overridable behavior. + ]], + }, -- HOOK_PLAYER_SPAWNED + + HOOK_PLAYER_TOSSING_ITEM = + { + CalledWhen = "A player is tossing an item. Plugin may override / refuse.", + DefaultFnName = "OnPlayerTossingItem", -- also used as pagename + Desc = [[ + This hook is called when a {{cPlayer|player}} has tossed an item (Q keypress). The + {{cPickup|pickup}} has not been spawned yet. Plugins may disallow the tossing, but in that case they + need to clean up - the player's client already thinks the item has been tossed so the + {{cInventory|inventory}} needs to be re-sent to the player.</p> + <p> + To get the item that is about to be tossed, call the {{cPlayer}}:GetEquippedItem() function. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player tossing an item" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called and finally MCServer + creates the pickup for the item and tosses it, using {{cPlayer}}:TossItem. If the function returns + true, no other callbacks are called for this event and MCServer doesn't toss the item. + ]], + }, -- HOOK_PLAYER_TOSSING_ITEM + + HOOK_PLAYER_USED_BLOCK = + { + CalledWhen = "A player has just used a block (chest, furnace…). Notification only.", + DefaultFnName = "OnPlayerUsedBlock", -- also used as pagename + Desc = [[ + This hook is called after a {{cPlayer|player}} has right-clicked a block that can be used, such as a + {{cChestEntity|chest}} or a lever. It is called after MCServer processes the usage (sends the UI + handling packets / toggles redstone). Note that for UI-related blocks, the player is most likely + still using the UI. This is a notification-only event.</p> + <p> + Note that the block coords given in this callback are for the (solid) block that is being clicked, + not the air block between it and the player.</p> + <p> + To get the world at which the right-click occurred, use the {{cPlayer}}:GetWorld() function.</p> + <p> + See also the {{OnPlayerUsingBlock|HOOK_PLAYER_USING_BLOCK}} for a similar hook called before the + use, the {{OnPlayerUsingItem|HOOK_PLAYER_USING_ITEM}} and {{OnPlayerUsedItem|HOOK_PLAYER_USED_ITEM}} + for similar hooks called when a player interacts with any block with a usable item in hand, such as + a bucket. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who used the block" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the clicked block" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the clicked block" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the clicked block" }, + { Name = "BlockFace", Type = "number", Notes = "Face of clicked block which has been clicked. One of the BLOCK_FACE_ constants" }, + { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor crosshair on the block being clicked" }, + { Name = "BlockType", Type = "number", Notes = "Block type of the clicked block" }, + { Name = "BlockMeta", Type = "number", Notes = "Block meta of the clicked block" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called for this event. + ]], + }, -- HOOK_PLAYER_USED_BLOCK + + HOOK_PLAYER_USED_ITEM = + { + CalledWhen = "A player has used an item in hand (bucket...)", + DefaultFnName = "OnPlayerUsedItem", -- also used as pagename + Desc = [[ + This hook is called after a {{cPlayer|player}} has right-clicked a block with an {{cItem|item}} that + can be used (is not placeable, is not food and clicked block is not use-able), such as a bucket or a + hoe. It is called after MCServer processes the usage (places fluid / turns dirt to farmland). + This is an information-only hook, there is no way to cancel the event anymore.</p> + <p> + Note that the block coords given in this callback are for the (solid) block that is being clicked, + not the air block between it and the player.</p> + <p> + To get the world at which the right-click occurred, use the {{cPlayer}}:GetWorld() function. To get + the item that the player is using, use the {{cPlayer}}:GetEquippedItem() function.</p> + <p> + See also the {{OnPlayerUsingItem|HOOK_PLAYER_USING_ITEM}} for a similar hook called before the use, + the {{OnPlayerUsingBlock|HOOK_PLAYER_USING_BLOCK}} and {{OnPlayerUsedBlock|HOOK_PLAYER_USED_BLOCK}} + for similar hooks called when a player interacts with a block, such as a chest. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who used the item" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the clicked block" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the clicked block" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the clicked block" }, + { Name = "BlockFace", Type = "number", Notes = "Face of clicked block which has been clicked. One of the BLOCK_FACE_ constants" }, + { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor crosshair on the block being clicked" }, + { Name = "BlockType", Type = "number", Notes = "Block type of the clicked block" }, + { Name = "BlockMeta", Type = "number", Notes = "Block meta of the clicked block" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called for this event. + ]], + }, -- HOOK_PLAYER_USED_ITEM + + HOOK_PLAYER_USING_BLOCK = + { + CalledWhen = "Just before a player uses a block (chest, furnace...). Plugin may override / refuse.", + DefaultFnName = "OnPlayerUsingBlock", -- also used as pagename + Desc = [[ + This hook is called when a {{cPlayer|player}} has right-clicked a block that can be used, such as a + {{cChestEntity|chest}} or a lever. It is called before MCServer processes the usage (sends the UI + handling packets / toggles redstone). Plugins may refuse the interaction by returning true.</p> + <p> + Note that the block coords given in this callback are for the (solid) block that is being clicked, + not the air block between it and the player.</p> + <p> + To get the world at which the right-click occurred, use the {{cPlayer}}:GetWorld() function.</p> + <p> + See also the {{OnPlayerUsedBlock|HOOK_PLAYER_USED_BLOCK}} for a similar hook called after the use, the + {{OnPlayerUsingItem|HOOK_PLAYER_USING_ITEM}} and {{OnPlayerUsedItem|HOOK_PLAYER_USED_ITEM}} for + similar hooks called when a player interacts with any block with a usable item in hand, such as a + bucket. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who is using the block" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the clicked block" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the clicked block" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the clicked block" }, + { Name = "BlockFace", Type = "number", Notes = "Face of clicked block which has been clicked. One of the BLOCK_FACE_ constants" }, + { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor crosshair on the block being clicked" }, + { Name = "BlockType", Type = "number", Notes = "Block type of the clicked block" }, + { Name = "BlockMeta", Type = "number", Notes = "Block meta of the clicked block" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called and then MCServer + processes the interaction. If the function returns true, no other callbacks are called for this + event and the interaction is silently dropped. + ]], + }, -- HOOK_PLAYER_USING_BLOCK + + HOOK_PLAYER_USING_ITEM = + { + CalledWhen = "Just before a player uses an item in hand (bucket...). Plugin may override / refuse.", + DefaultFnName = "OnPlayerUsingItem", -- also used as pagename + Desc = [[ + This hook is called when a {{cPlayer|player}} has right-clicked a block with an {{cItem|item}} that + can be used (is not placeable, is not food and clicked block is not use-able), such as a bucket or a + hoe. It is called before MCServer processes the usage (places fluid / turns dirt to farmland). + Plugins may refuse the interaction by returning true.</p> + <p> + Note that the block coords given in this callback are for the (solid) block that is being clicked, + not the air block between it and the player.</p> + <p> + To get the world at which the right-click occurred, use the {{cPlayer}}:GetWorld() function. To get + the item that the player is using, use the {{cPlayer}}:GetEquippedItem() function.</p> + <p> + See also the {{OnPlayerUsedItem|HOOK_PLAYER_USED_ITEM}} for a similar hook called after the use, the + {{OnPlayerUsingBlock|HOOK_PLAYER_USING_BLOCK}} and {{OnPlayerUsedBlock|HOOK_PLAYER_USED_BLOCK}} for + similar hooks called when a player interacts with a block, such as a chest. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who is using the item" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the clicked block" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the clicked block" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the clicked block" }, + { Name = "BlockFace", Type = "number", Notes = "Face of clicked block which has been clicked. One of the BLOCK_FACE_ constants" }, + { Name = "CursorX", Type = "number", Notes = "X-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorY", Type = "number", Notes = "Y-coord of the cursor crosshair on the block being clicked" }, + { Name = "CursorZ", Type = "number", Notes = "Z-coord of the cursor crosshair on the block being clicked" }, + { Name = "BlockType", Type = "number", Notes = "Block type of the clicked block" }, + { Name = "BlockMeta", Type = "number", Notes = "Block meta of the clicked block" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called and then MCServer + processes the interaction. If the function returns true, no other callbacks are called for this + event and the interaction is silently dropped. + ]], + }, -- HOOK_PLAYER_USING_ITEM + + HOOK_POST_CRAFTING = + { + CalledWhen = "After the built-in recipes are checked and a recipe was found.", + DefaultFnName = "OnPostCrafting", -- also used as pagename + Desc = [[ + This hook is called when a {{cPlayer|player}} changes contents of their + {{cCraftingGrid|crafting grid}}, after the recipe has been established by MCServer. Plugins may use + this to modify the resulting recipe or provide an alternate recipe.</p> + <p> + If a plugin implements custom recipes, it should do so using the {{OnPreCrafting|HOOK_PRE_CRAFTING}} + hook, because that will save the server from going through the built-in recipes. The + HOOK_POST_CRAFTING hook is intended as a notification, with a chance to tweak the result.</p> + <p> + Note that this hook is not called if a built-in recipe is not found; + {{OnCraftingNoRecipe|HOOK_CRAFTING_NO_RECIPE}} is called instead in such a case. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has changed their crafting grid contents" }, + { Name = "Grid", Type = "{{cCraftingGrid}}", Notes = "The new crafting grid contents" }, + { Name = "Recipe", Type = "{{cCraftingRecipe}}", Notes = "The recipe that MCServer has decided to use (can be tweaked by plugins)" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called for this event. In either case, MCServer uses the value + of Recipe as the recipe to be presented to the player. + ]], + }, -- HOOK_POST_CRAFTING + + HOOK_PRE_CRAFTING = + { + CalledWhen = "Before the built-in recipes are checked.", + DefaultFnName = "OnPreCrafting", -- also used as pagename + Desc = [[ + This hook is called when a {{cPlayer|player}} changes contents of their + {{cCraftingGrid|crafting grid}}, before the built-in recipes are searched for a match by MCServer. + Plugins may use this hook to provide a custom recipe.</p> + <p> + If you intend to tweak built-in recipes, use the {{OnPostCrafting|HOOK_POST_CRAFTING}} hook, because + that will be called once the built-in recipe is matched.</p> + <p> + Also note a third hook, {{OnCraftingNoRecipe|HOOK_CRAFTING_NO_RECIPE}}, that is called when MCServer + cannot find any built-in recipe for the given ingredients. + ]], + Params = + { + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who has changed their crafting grid contents" }, + { Name = "Grid", Type = "{{cCraftingGrid}}", Notes = "The new crafting grid contents" }, + { Name = "Recipe", Type = "{{cCraftingRecipe}}", Notes = "The recipe that MCServer will use. Modify this object to change the recipe" }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called and then MCServer + searches the built-in recipes. The Recipe output parameter is ignored in this case.</p> + <p> + If the function returns true, no other callbacks are called for this event and MCServer uses the + recipe stored in the Recipe output parameter. + ]], + }, -- HOOK_PRE_CRAFTING + + HOOK_SPAWNED_ENTITY = + { + CalledWhen = "After an entity is spawned in the world.", + DefaultFnName = "OnSpawnedEntity", -- also used as pagename + Desc = [[ + This hook is called after the server spawns an {{cEntity|entity}}. This is an information-only + callback, the entity is already spawned by the time it is called. If the entity spawned is a + {{cMonster|monster}}, the {{OnSpawnedMonster|HOOK_SPAWNED_MONSTER}} hook is called before this + hook.</p> + <p> + See also the {{OnSpawningEntity|HOOK_SPAWNING_ENTITY}} hook for a similar hook called before the + entity is spawned. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the entity has spawned" }, + { Name = "Entity", Type = "{{cEntity}} descentant", Notes = "The entity that has spawned" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. If the function + returns true, no other callback is called for this event. + ]], + }, -- HOOK_SPAWNED_ENTITY + + HOOK_SPAWNED_MONSTER = + { + CalledWhen = "After a monster is spawned in the world", + DefaultFnName = "OnSpawnedMonster", -- also used as pagename + Desc = [[ + This hook is called after the server spawns a {{cMonster|monster}}. This is an information-only + callback, the monster is already spawned by the time it is called. After this hook is called, the + {{OnSpawnedEntity|HOOK_SPAWNED_ENTITY}} is called for the monster entity.</p> + <p> + See also the {{OnSpawningMonster|HOOK_SPAWNING_MONSTER}} hook for a similar hook called before the + monster is spawned. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the monster has spawned" }, + { Name = "Monster", Type = "{{cMonster}} descendant", Notes = "The monster that has spawned" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. If the function + returns true, no other callback is called for this event. + ]], + }, -- HOOK_SPAWNED_MONSTER + + HOOK_SPAWNING_ENTITY = + { + CalledWhen = "Before an entity is spawned in the world.", + DefaultFnName = "OnSpawningEntity", -- also used as pagename + Desc = [[ + This hook is called before the server spawns an {{cEntity|entity}}. The plugin can either modify the + entity before it is spawned, or disable the spawning altogether. If the entity spawning is a + monster, the {{OnSpawningMonster|HOOK_SPAWNING_MONSTER}} hook is called before this hook.</p> + <p> + See also the {{OnSpawnedEntity|HOOK_SPAWNED_ENTITY}} hook for a similar hook called after the + entity is spawned. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the entity will spawn" }, + { Name = "Entity", Type = "{{cEntity}} descentant", Notes = "The entity that will spawn" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. Finally, the server + spawns the entity with whatever parameters have been set on the {{cEntity}} object by the callbacks. + If the function returns true, no other callback is called for this event and the entity is not + spawned. + ]], + }, -- HOOK_SPAWNING_ENTITY + + HOOK_SPAWNING_MONSTER = + { + CalledWhen = "Before a monster is spawned in the world.", + DefaultFnName = "OnSpawningMonster", -- also used as pagename + Desc = [[ + This hook is called before the server spawns a {{cMonster|monster}}. The plugins may modify the + monster's parameters in the {{cMonster}} class, or disallow the spawning altogether. This hook is + called before the {{OnSpawningEntity|HOOK_SPAWNING_ENTITY}} is called for the monster entity.</p> + <p> + See also the {{OnSpawnedMonster|HOOK_SPAWNED_MONSTER}} hook for a similar hook called after the + monster is spawned. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the entity will spawn" }, + { Name = "Monster", Type = "{{cMonster}} descentant", Notes = "The monster that will spawn" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. Finally, the server + spawns the monster with whatever parameters the plugins set in the cMonster parameter.</p> + <p> + If the function returns true, no other callback is called for this event and the monster won't + spawn. + ]], + }, -- HOOK_SPAWNING_MONSTER + + HOOK_TAKE_DAMAGE = + { + CalledWhen = "An {{cEntity|entity}} is taking any kind of damage", + DefaultFnName = "OnTakeDamage", -- also used as pagename + Desc = [[ + This hook is called when any {{cEntity}} descendant, such as a {{cPlayer|player}} or a + {{cMonster|mob}}, takes any kind of damage. The plugins may modify the amount of damage or effects + with this hook by editting the {{TakeDamageInfo}} object passed.</p> + <p> + This hook is called after the final damage is calculated, including all the possible weapon + {{cEnchantments|enchantments}}, armor protection and potion effects. + ]], + Params = + { + { Name = "Receiver", Type = "{{cEntity}} descendant", Notes = "The entity taking damage" }, + { Name = "TDI", Type = "{{TakeDamageInfo}}", Notes = "The damage type, cause and effects. Plugins may modify this object to alter the final damage applied." }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called and then the server + applies the final values from the TDI object to Receiver. If the function returns true, no other + callbacks are called, and no damage nor effects are applied. + ]], + }, -- HOOK_TAKE_DAMAGE + + HOOK_TICK = + { + CalledWhen = "Every server tick (approximately 20 times per second)", + DefaultFnName = "OnTick", -- also used as pagename + Desc = [[ + This hook is called every game tick (50 msec, or 20 times a second). If the server is overloaded, + the interval is larger, which is indicated by the TimeDelta parameter.</p> + <p> + This hook is called in the context of the server-tick thread, that is, the thread that takes care of + {{cClientHandle|client connections}} before they're assigned to {{cPlayer|player entities}}, and + processing console commands. + ]], + Params = + { + { Name = "TimeDelta", Type = "number", Notes = "The number of milliseconds elapsed since the last server tick. Will not be less than 50 msec." }, + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called. There is no overridable behavior. + ]], + }, -- HOOK_TICK + + HOOK_UPDATED_SIGN = + { + CalledWhen = "After the sign text is updated. Notification only.", + DefaultFnName = "OnUpdatedSign", -- also used as pagename + Desc = [[ + This hook is called after a sign has had its text updated. The text is already updated at this + point.</p> + <p>The update may have been caused either by a {{cPlayer|player}} directly updating the sign, or by + a plugin changing the sign text using the API.</p> + <p> + See also the {{OnUpdatingSign|HOOK_UPDATING_SIGN}} hook for a similar hook called before the update, + with a chance to modify the text. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the sign resides" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the sign" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the sign" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the sign" }, + { Name = "Line1", Type = "string", Notes = "1st line of the new text" }, + { Name = "Line2", Type = "string", Notes = "2nd line of the new text" }, + { Name = "Line3", Type = "string", Notes = "3rd line of the new text" }, + { Name = "Line4", Type = "string", Notes = "4th line of the new text" }, + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who is changing the text. May be nil for non-player updates." } + }, + Returns = [[ + If the function returns false or no value, other plugins' callbacks are called. If the function + returns true, no other callbacks are called. There is no overridable behavior. + ]], + }, -- HOOK_UPDATED_SIGN + HOOK_UPDATING_SIGN = + { + CalledWhen = "Before the sign text is updated. Plugin may modify the text / refuse.", + DefaultFnName = "OnUpdatingSign", -- also used as pagename + Desc = [[ + This hook is called when a sign text is about to be updated, either as a result of player's + manipulation or any other event, such as a plugin setting the sign text. Plugins may modify the text + or refuse the update altogether.</p> + <p> + See also the {{OnUpdatedSign|HOOK_UPDATED_SIGN}} hook for a similar hook called after the update. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "The world in which the sign resides" }, + { Name = "BlockX", Type = "number", Notes = "X-coord of the sign" }, + { Name = "BlockY", Type = "number", Notes = "Y-coord of the sign" }, + { Name = "BlockZ", Type = "number", Notes = "Z-coord of the sign" }, + { Name = "Line1", Type = "string", Notes = "1st line of the new text" }, + { Name = "Line2", Type = "string", Notes = "2nd line of the new text" }, + { Name = "Line3", Type = "string", Notes = "3rd line of the new text" }, + { Name = "Line4", Type = "string", Notes = "4th line of the new text" }, + { Name = "Player", Type = "{{cPlayer}}", Notes = "The player who is changing the text. May be nil for non-player updates." } + }, + Returns = [[ + The function may return up to five values. If the function returns true as the first value, no other + callbacks are called for this event and the sign is not updated. If the function returns no value or + false as its first value, other plugins' callbacks are called.</p> + <p> + The other up to four values returned are used to update the sign text, line by line, respectively. + Note that other plugins may again update the texts (if the first value returned is false). + ]], + CodeExamples = + { + { + Title = "Add player signature", + Desc = "The following example appends a player signature to the last line, if the sign is updated by a player:", + Code = [[ +function OnUpdatingSign(World, BlockX, BlockY, BlockZ, Line1, Line2, Line3, Line4, Player) + if (Player == nil) then + -- Not changed by a player + return false; + end + + -- Sign with playername, allow other plugins to interfere: + return false, Line1, Line2, Line3, Line4 .. Player:GetName(); +end + ]], + } + } , + }, -- HOOK_UPDATING_SIGN + + HOOK_WEATHER_CHANGED = + { + CalledWhen = "The weather has changed", + DefaultFnName = "OnWeatherChanged", -- also used as pagename + Desc = [[ + This hook is called after the weather has changed in a {{cWorld|world}}. The new weather has already + been sent to the clients.</p> + <p> + See also the {{OnWeatherChanging|HOOK_WEATHER_CHANGING}} hook for a similar hook called before the + change. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "World for which the weather has changed" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. If the function + returns true, no other callback is called for this event. There is no overridable behavior. + ]], + }, -- HOOK_WEATHER_CHANGED + + HOOK_WEATHER_CHANGING = + { + CalledWhen = "The weather is about to change", + DefaultFnName = "OnWeatherChanging", -- also used as pagename + Desc = [[ + This hook is called when the current weather has expired and a new weather is selected. Plugins may + override the new weather setting.</p> + <p> + The new weather setting is sent to the clients only after this hook has been processed.</p> + <p> + See also the {{OnWeatherChanged|HOOK_WEATHER_CHANGED}} hook for a similar hook called after the + change. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "World for which the weather is changing" }, + { Name = "Weather", Type = "number", Notes = "The newly selected weather. One of wSunny, wRain, wStorm" }, + }, + Returns = [[ + If the function returns false or no value, the server calls other plugins' callbacks and finally + sets the weather. If the function returns true, the server takes the second returned value (wSunny + by default) and sets it as the new weather. No other plugins' callbacks are called in this case. + ]], + }, -- HOOK_WEATHER_CHANGING + + HOOK_WORLD_TICK = + { + CalledWhen = "Every world tick (about 20 times per second), separately for each world", + DefaultFnName = "OnWorldTick", -- also used as pagename + Desc = [[ + This hook is called for each {{cWorld|world}} every tick (50 msec, or 20 times a second). If the + world is overloaded, the interval is larger, which is indicated by the TimeDelta parameter.</p> + <p> + This hook is called in the world's tick thread context and thus has access to all world data + guaranteed without blocking. + ]], + Params = + { + { Name = "World", Type = "{{cWorld}}", Notes = "World that is ticking" }, + { Name = "TimeDelta", Type = "number", Notes = "The number of milliseconds since the previous game tick. Will not be less than 50 msec" }, + }, + Returns = [[ + If the function returns false or no value, the next plugin's callback is called. If the function + returns true, no other callback is called for this event. There is no overridable behavior. + ]], + }, -- HOOK_WORLD_TICK + }, -- Hooks[] diff --git a/MCServer/Plugins/APIDump/WebWorldThreads.html b/MCServer/Plugins/APIDump/WebWorldThreads.html index a77209b0b..7cc94e9fa 100644 --- a/MCServer/Plugins/APIDump/WebWorldThreads.html +++ b/MCServer/Plugins/APIDump/WebWorldThreads.html @@ -1,64 +1,64 @@ +<!DOCTYPE html> <html> -<head> -<title>MCServer - Webserver vs World threads</title> -<script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> -<script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> -</head> -<body> + <head> + <title>MCServer - Webserver vs World threads</title> + <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> + <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> + </head> + <body> + <h1>Webserver vs World threads</h1> + <p> + This article will explain the threading issues that arise between the webserver and world threads are of concern to plugin authors.</p> + <p> + Generally, plugins that provide webadmin pages should be quite careful about their interactions. Most operations on MCServer objects requires synchronization, that MCServer provides automatically and transparently to plugins - when a block is written, the chunkmap is locked, or when an entity is being manipulated, the entity list is locked. Each plugin also has a mutex lock, so that only one thread at a time may be executing plugin code.</p> + <p> + This locking can be a source of deadlocks for plugins that are not written carefully.</p> -<h1>Webserver vs World threads</h1> -<p> -This article will explain the threading issues that arise between the webserver and world threads are of concern to plugin authors.</p> -<p> -Generally, plugins that provide webadmin pages should be quite careful about their interactions. Most operations on MCServer objects requires synchronization, that MCServer provides automatically and transparently to plugins - when a block is written, the chunkmap is locked, or when an entity is being manipulated, the entity list is locked. Each plugin also has a mutex lock, so that only one thread at a time may be executing plugin code.</p> -<p> -This locking can be a source of deadlocks for plugins that are not written carefully.</p> + <h2>Example scenario</h2> + <p>Consider the following example. A plugin provides a webadmin page that allows the admin to kick players off the server. When the admin presses the "Kick" button, the plugin calls cWorld:DoWithPlayer() with a callback to kick the player. Everything seems to be working fine now.</p> + <p> + A new feature is developed in the plugin, now the plugin adds a new in-game command so that the admins can kick players while they're playing the game. The plugin registers a command callback with cPluginManager.AddCommand(). Now there are problems bound to happen.</p> + <p> + Suppose that two admins are in, one is using the webadmin and the other is in-game. Both try to kick a player at the same time. The webadmin locks the plugin, so that it can execute the plugin code, but right at this moment the OS switches threads. The world thread locks the world so that it can access the list of in-game commands, receives the in-game command, it tries to lock the plugin. The plugin is already locked, so the world thread is put on hold. After a while, the webadmin thread is woken up again and continues processing. It tries to lock the world so that it can traverse the playerlist, but the lock is already held by the world thread. Now both threads are holding one lock each and trying to grab the other lock, and are therefore deadlocked.</p> -<h2>Example scenario</h2> -<p>Consider the following example. A plugin provides a webadmin page that allows the admin to kick players off the server. When the admin presses the "Kick" button, the plugin calls cWorld:DoWithPlayer() with a callback to kick the player. Everything seems to be working fine now.</p> -<p> -A new feature is developed in the plugin, now the plugin adds a new in-game command so that the admins can kick players while they're playing the game. The plugin registers a command callback with cPluginManager.AddCommand(). Now there are problems bound to happen.</p> -<p> -Suppose that two admins are in, one is using the webadmin and the other is in-game. Both try to kick a player at the same time. The webadmin locks the plugin, so that it can execute the plugin code, but right at this moment the OS switches threads. The world thread locks the world so that it can access the list of in-game commands, receives the in-game command, it tries to lock the plugin. The plugin is already locked, so the world thread is put on hold. After a while, the webadmin thread is woken up again and continues processing. It tries to lock the world so that it can traverse the playerlist, but the lock is already held by the world thread. Now both threads are holding one lock each and trying to grab the other lock, and are therefore deadlocked.</p> + <h2>How to avoid the deadlock</h2> + <p> + There are two main ways to avoid such a deadlock. The first approach is using tasks: Everytime you need to execute a task inside a world, instead of executing it, queue it, using <a href="cWorld.html">cWorld</a>:QueueTask(). This handy utility can will call the given function inside the world's TickThread, thus eliminating the deadlock, because now there's only one thread. However, this approach will not let you get data back. You cannot query the player list, or the entities, or anything - because when the task runs, the webadmin page has already been served to the browser.</p> + <p> + To accommodate this, you'll need to use the second approach - preparing and caching data in the tick thread, possibly using callbacks. This means that the plugin will have global variables that will store the data, and update those variables when the data changes; then the webserver thread will only read those variables, instead of calling the world functions. For example, if a webpage was to display the list of currently connected players, the plugin should maintain a global variable, g_WorldPlayers, which would be a table of worlds, each item being a list of currently connected players. The webadmin handler would read this variable and create the page from it; the plugin would use HOOK_PLAYER_JOINED and HOOK_DISCONNECT to update the variable.</p> -<h2>How to avoid the deadlock</h2> -<p> -There are two main ways to avoid such a deadlock. The first approach is using tasks: Everytime you need to execute a task inside a world, instead of executing it, queue it, using <a href="cWorld.html">cWorld</a>:QueueTask(). This handy utility can will call the given function inside the world's TickThread, thus eliminating the deadlock, because now there's only one thread. However, this approach will not let you get data back. You cannot query the player list, or the entities, or anything - because when the task runs, the webadmin page has already been served to the browser.</p> -<p> -To accommodate this, you'll need to use the second approach - preparing and caching data in the tick thread, possibly using callbacks. This means that the plugin will have global variables that will store the data, and update those variables when the data changes; then the webserver thread will only read those variables, instead of calling the world functions. For example, if a webpage was to display the list of currently connected players, the plugin should maintain a global variable, g_WorldPlayers, which would be a table of worlds, each item being a list of currently connected players. The webadmin handler would read this variable and create the page from it; the plugin would use HOOK_PLAYER_JOINED and HOOK_DISCONNECT to update the variable.</p> + <h2>What to avoid</h2> + <p> + Now that we know what the danger is and how to avoid it, how do we know if our code is susceptible?</p> + <p> + The general rule of thumb is to avoid calling any functions that read or write lists of things in the webserver thread. This means most ForEach() and DoWith() functions. Only <a href="cRoot.html">cRoot</a>:ForEachWorld() is safe - because the list of worlds is not expected to change, so it is not guarded by a mutex. Getting and setting world's blocks is, naturally, unsafe, as is calling other plugins, or creating entities.</p> -<h2>What to avoid</h2> -<p> -Now that we know what the danger is and how to avoid it, how do we know if our code is susceptible?</p> -<p> -The general rule of thumb is to avoid calling any functions that read or write lists of things in the webserver thread. This means most ForEach() and DoWith() functions. Only <a href="cRoot.html">cRoot</a>:ForEachWorld() is safe - because the list of worlds is not expected to change, so it is not guarded by a mutex. Getting and setting world's blocks is, naturally, unsafe, as is calling other plugins, or creating entities.</p> - -<h2>Example</h2> -The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler): -<pre class="prettyprint lang-lua"> -local KickPlayerName = Request.Params["players-kick"] -local FoundPlayerCallback = function(Player) - if (Player:GetName() == KickPlayerName) then - Player:GetClientHandle():Kick("You were kicked from the game!") - end -end -cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback) -</pre> -The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds: -<pre class="prettyprint lang-lua"> -cRoot:Get():ForEachWorld( -- For each world... - function(World) - World:QueueTask( -- ... queue a task... - function(a_World) - a_World:DoWithPlayer(KickPlayerName, -- ... to walk the playerlist... - function (a_Player) - a_Player:GetClientHandle():Kick("You were kicked from the game!") -- ... and kick the player - end - ) - end - ) - end -) -</pre> -</body> + <h2>Example</h2> + The Core has the facility to kick players using the web interface. It used the following code for the kicking (inside the webadmin handler): + <pre class="prettyprint lang-lua"> + local KickPlayerName = Request.Params["players-kick"] + local FoundPlayerCallback = function(Player) + if (Player:GetName() == KickPlayerName) then + Player:GetClientHandle():Kick("You were kicked from the game!") + end + end + cRoot:Get():FindAndDoWithPlayer(KickPlayerName, FoundPlayerCallback) + </pre> + The cRoot:FindAndDoWithPlayer() is unsafe and could have caused a deadlock. The new solution is queue a task; but since we don't know in which world the player is, we need to queue the task to all worlds: + <pre class="prettyprint lang-lua"> + cRoot:Get():ForEachWorld( -- For each world... + function(World) + World:QueueTask( -- ... queue a task... + function(a_World) + a_World:DoWithPlayer(KickPlayerName, -- ... to walk the playerlist... + function (a_Player) + a_Player:GetClientHandle():Kick("You were kicked from the game!") -- ... and kick the player + end + ) + end + ) + end + ) + </pre> + </body> </html>
\ No newline at end of file diff --git a/MCServer/Plugins/APIDump/main.css b/MCServer/Plugins/APIDump/main.css index 777f6d71a..5cc603a3f 100644 --- a/MCServer/Plugins/APIDump/main.css +++ b/MCServer/Plugins/APIDump/main.css @@ -1,3 +1,8 @@ +html +{ + background-color: #C0C0C0; +} + table { background-color: #fff; @@ -25,4 +30,27 @@ pre { border: 1px solid #ccc; background-color: #eee; -}
\ No newline at end of file +} + +body +{ + min-width: 800px; + width: 95%; + margin: 10px auto; + background-color: white; + border: 4px #FF8C00 solid; + border-radius: 20px; + font-family: Calibri, Trebuchet MS; +} + +header +{ + text-align: center; + font-family: Segoe UI Light, Helvetica; +} + +#content +{ + padding: 0px 25px 25px 25px; +} + diff --git a/MCServer/Plugins/APIDump/main.lua b/MCServer/Plugins/APIDump/main.lua index 6ae4a6b0f..22c7ad764 100644 --- a/MCServer/Plugins/APIDump/main.lua +++ b/MCServer/Plugins/APIDump/main.lua @@ -10,6 +10,7 @@ -- Global variables: g_Plugin = nil; g_PluginFolder = ""; +g_TrackedPages = {}; -- List of tracked pages, to be checked later whether they exist. Each item is an array of referring pagenames. @@ -22,7 +23,7 @@ function Initialize(Plugin) Plugin:SetName("APIDump"); Plugin:SetVersion(1); - LOG("Initialized " .. Plugin:GetName() .. " v." .. Plugin:GetVersion()) + LOG("Initialised " .. Plugin:GetName() .. " v." .. Plugin:GetVersion()) g_PluginFolder = Plugin:GetLocalFolder(); @@ -212,48 +213,69 @@ function DumpAPIHtml() return; end - f:write([[<html><head><title>MCServer API - index</title> - <link rel="stylesheet" type="text/css" href="main.css" /> - </head><body><h1>MCServer API - index</h1> - <p>The API reference is divided into the following sections:<ul> - <li><a href="#classes">Class index</a></li> - <li><a href="#hooks">Hooks</a></li> - <li><a href="#extra">Extra pages</a></li> - </ul></p> - <a name="classes"><h2>Class index</h2></a> - <p>The following classes are available in the MCServer Lua scripting language: - <ul> - ]]); + f:write([[<!DOCTYPE html> +<html> + <head> + <title>MCServer API - Index</title> + <link rel="stylesheet" type="text/css" href="main.css" /> + </head> + <body> + <div id="content"> + <header> + <h1>MCServer API - Index</h1> + <hr /> + </header> + <p>The API reference is divided into the following sections:</p> + + <ul> + <li><a href="#classes">Class index</a></li> + <li><a href="#hooks">Hooks</a></li> + <li><a href="#extra">Extra pages</a></li> + </ul> + + <hr /> + <a name="classes"><h2>Class index</h2></a> + <p>The following classes are available in the MCServer Lua scripting language:</p> + + <ul> +]]); for i, cls in ipairs(API) do - f:write("<li><a href=\"" .. cls.Name .. ".html\">" .. cls.Name .. "</a></li>\n"); + f:write(" <li><a href=\"" .. cls.Name .. ".html\">" .. cls.Name .. "</a></li>\n"); WriteHtmlClass(cls, API); end - f:write([[</ul></p> - <a name="hooks"><h2>Hooks</h2></a> - <p>A plugin can register to be called whenever an “interesting event” occurs. It does so by calling - <a href="cPluginManager.html">cPluginManager</a>'s AddHook() function and implementing a callback - function to handle the event.</p> - <p>A plugin can decide whether it will let the event pass through to the rest of the plugins, or hide it - from them. This is determined by the return value from the hook callback function. If the function returns - false or no value, the event is propagated further. If the function returns true, the processing is - stopped, no other plugin receives the notification (and possibly MCServer disables the default behavior - for the event). See each hook's details to see the exact behavior.</p> - <table><tr><th>Hook name</th><th>Called when</th></tr> - ]]); + f:write([[ </ul> + + <hr /> + <a name="hooks"><h2>Hooks</h2></a> + + <p>A plugin can register to be called whenever an "interesting event" occurs. It does so by calling <a href="cPluginManager.html">cPluginManager</a>'s AddHook() function and implementing a callback function to handle the event.</p> + <p>A plugin can decide whether it will let the event pass through to the rest of the plugins, or hide it from them. This is determined by the return value from the hook callback function. If the function returns false or no value, the event is propagated further. If the function returns true, the processing is stopped, no other plugin receives the notification (and possibly MCServer disables the default behavior for the event). See each hook's details to see the exact behavior.</p> + + <table> + <tr> + <th>Hook name</th> + <th>Called when</th> + </tr> +]]); for i, hook in ipairs(Hooks) do if (hook.DefaultFnName == nil) then -- The hook is not documented yet - f:write("<tr><td>" .. hook.Name .. "</td><td><i>(No documentation yet)</i></td></tr>\n"); + f:write(" <tr>\n <td>" .. hook.Name .. "</td>\n <td><i>(No documentation yet)</i></td>\n </tr>\n"); table.insert(UndocumentedHooks, hook.Name); else - f:write("<tr><td><a href=\"" .. hook.DefaultFnName .. ".html\">" .. hook.Name .. "</a></td><td>" .. LinkifyString(hook.CalledWhen) .. "</td></tr>\n"); + f:write(" <tr>\n <td><a href=\"" .. hook.DefaultFnName .. ".html\">" .. hook.Name .. "</a></td>\n <td>" .. LinkifyString(hook.CalledWhen, hook.Name) .. "</td>\n </tr>\n"); WriteHtmlHook(hook); end end - f:write([[</table> - <a name="extra"><h2>Extra pages</h2></a> - <p>The following pages provide various extra information</p> - <ul>]]); + f:write([[ </table> + + <hr /> + <a name="extra"><h2>Extra pages</h2></a> + + <p>The following pages provide various extra information</p> + + <ul> +]]); for i, extra in ipairs(g_APIDesc.ExtraPages) do local SrcFileName = g_PluginFolder .. "/" .. extra.FileName; if (cFile:Exists(SrcFileName)) then @@ -262,14 +284,15 @@ function DumpAPIHtml() cFile:Delete(DstFileName); end cFile:Copy(SrcFileName, DstFileName); - f:write("<li><a href=\"" .. extra.FileName .. "\">" .. extra.Title .. "</a></li>\n"); + f:write(" <li><a href=\"" .. extra.FileName .. "\">" .. extra.Title .. "</a></li>\n"); else - f:write("<li>" .. extra.Title .. " <i>(file is missing)</i></li>\n"); + f:write(" <li>" .. extra.Title .. " <i>(file is missing)</i></li>\n"); end end - f:write([[</ul> - </body></html> - ]]); + f:write([[ </ul> + </div> + </body> +</html>]]); f:close(); -- Copy the CSS file to the output folder (overwrite any existing): @@ -285,7 +308,7 @@ function DumpAPIHtml() end -- List the undocumented objects: - f = io.open("API/undocumented.lua", "w"); + f = io.open("API/_undocumented.lua", "w"); if (f ~= nil) then f:write("\n-- This is the list of undocumented API objects, automatically generated by APIDump\n\n"); f:write("g_APIDesc =\n{\n\tClasses =\n\t{\n"); @@ -351,7 +374,7 @@ function DumpAPIHtml() end -- List the unexported documented API objects: - f = io.open("API/unexported-documented.txt", "w"); + f = io.open("API/_unexported-documented.txt", "w"); if (f ~= nil) then for clsname, cls in pairs(g_APIDesc.Classes) do if not(cls.IsExported) then @@ -377,6 +400,9 @@ function DumpAPIHtml() f:close(); end + -- List the missing pages + ListMissingPages(); + LOG("API subfolder written"); end @@ -616,9 +642,52 @@ end -- Make a link out of anything with the special linkifying syntax {{link|title}} -function LinkifyString(a_String) - local txt = a_String:gsub("{{([^|}]*)|([^}]*)}}", "<a href=\"%1.html\">%2</a>") -- {{link|title}} - txt = txt:gsub("{{([^|}]*)}}", "<a href=\"%1.html\">%1</a>") -- {{LinkAndTitle}} +function LinkifyString(a_String, a_Referrer) + assert(a_Referrer ~= nil); + assert(a_Referrer ~= ""); + + --- Adds a page to the list of tracked pages (to be checked for existence at the end) + local function AddTrackedPage(a_PageName) + local Pg = (g_TrackedPages[a_PageName] or {}); + table.insert(Pg, a_Referrer); + g_TrackedPages[a_PageName] = Pg; + end + + --- Creates the HTML for the specified link and title + local function CreateLink(Link, Title) + if (Link:sub(1, 7) == "http://") then + -- The link is a full absolute URL, do not modify, do not track: + return "<a href=\"" .. Link .. "\">" .. Title .. "</a>"; + end + local idxHash = Link:find("#"); + if (idxHash ~= nil) then + -- The link contains an anchor: + if (idxHash == 1) then + -- Anchor in the current page, no need to track: + return "<a href=\"" .. Link .. "\">" .. Title .. "</a>"; + end + -- Anchor in another page: + local PageName = Link:sub(1, idxHash - 1); + AddTrackedPage(PageName); + return "<a href=\"" .. PageName .. ".html#" .. Link:sub(idxHash + 1) .. "\">" .. Title .. "</a>"; + end + -- Link without anchor: + AddTrackedPage(Link); + return "<a href=\"" .. Link .. ".html\">" .. Title .. "</a>"; + end + + -- Linkify the strings using the CreateLink() function: + local txt = a_String:gsub("{{([^|}]*)|([^}]*)}}", CreateLink) -- {{link|title}} + txt = txt:gsub("{{([^|}]*)}}", -- {{LinkAndTitle}} + function(LinkAndTitle) + local idxHash = LinkAndTitle:find("#"); + if (idxHash ~= nil) then + -- The LinkAndTitle contains a hash, remove the hashed part from the title: + return CreateLink(LinkAndTitle, LinkAndTitle:sub(1, idxHash - 1)); + end + return CreateLink(LinkAndTitle, LinkAndTitle); + end + ); return txt; end @@ -639,16 +708,16 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI) end if (a_InheritedName ~= nil) then - cf:write("<h2>Functions inherited from " .. a_InheritedName .. "</h2>"); + cf:write(" <h2>Functions inherited from " .. a_InheritedName .. "</h2>\n"); end - cf:write("<table><tr><th>Name</th><th>Parameters</th><th>Return value</th><th>Notes</th></tr>\n"); + cf:write(" <table>\n <tr>\n <th>Name</th>\n <th>Parameters</th>\n <th>Return value</th>\n <th>Notes</th>\n </tr>\n"); for i, func in ipairs(a_Functions) do - cf:write("<tr><td>" .. func.Name .. "</td>"); - cf:write("<td>" .. LinkifyString(func.Params or "").. "</td>"); - cf:write("<td>" .. LinkifyString(func.Return or "").. "</td>"); - cf:write("<td>" .. LinkifyString(func.Notes or "") .. "</td></tr>\n"); + cf:write(" <tr>\n <td>" .. func.Name .. "</td>\n"); + cf:write(" <td>" .. LinkifyString(func.Params or "", (a_InheritedName or a_ClassAPI.Name)).. "</td>\n"); + cf:write(" <td>" .. LinkifyString(func.Return or "", (a_InheritedName or a_ClassAPI.Name)).. "</td>\n"); + cf:write(" <td>" .. LinkifyString(func.Notes or "", (a_InheritedName or a_ClassAPI.Name)) .. "</td>\n </tr>\n"); end - cf:write("</table>\n"); + cf:write(" </table>\n\n"); end local function WriteDescendants(a_Descendants) @@ -663,6 +732,8 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI) end cf:write("</ul>\n"); end + + local ClassName = a_ClassAPI.Name; -- Build an array of inherited classes chain: local InheritanceChain = {}; @@ -672,67 +743,77 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI) CurrInheritance = CurrInheritance.Inherits; end - cf:write([[<html><head><title>MCServer API - ]] .. a_ClassAPI.Name .. [[ class</title> - <link rel="stylesheet" type="text/css" href="main.css" /> - <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> - <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> - </head><body> - <h1>Contents</h1> - <ul> - ]]); + cf:write([[<!DOCTYPE html> +<html> + <head> + <title>MCServer API - ]] .. a_ClassAPI.Name .. [[ Class</title> + <link rel="stylesheet" type="text/css" href="main.css" /> + <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> + <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> + </head> + <body> + <div id="content"> + <header> + <h1>]] .. a_ClassAPI.Name .. [[</h1> + <hr /> + </header> + <h1>Contents</h1> + + <ul> +]]); local HasInheritance = ((#a_ClassAPI.Descendants > 0) or (a_ClassAPI.Inherits ~= nil)); -- Write the table of contents: if (HasInheritance) then - cf:write("<li><a href=\"#inherits\">Inheritance</a></li>\n"); + cf:write(" <li><a href=\"#inherits\">Inheritance</a></li>\n"); end - cf:write("<li><a href=\"#constants\">Constants</a></li>\n"); - cf:write("<li><a href=\"#functions\">Functions</a></li>\n"); + cf:write(" <li><a href=\"#constants\">Constants</a></li>\n"); + cf:write(" <li><a href=\"#functions\">Functions</a></li>\n"); if (a_ClassAPI.AdditionalInfo ~= nil) then for i, additional in ipairs(a_ClassAPI.AdditionalInfo) do - cf:write("<li><a href=\"#additionalinfo_" .. i .. "\">" .. additional.Header .. "</a></li>\n"); + cf:write(" <li><a href=\"#additionalinfo_" .. i .. "\">" .. additional.Header .. "</a></li>\n"); end end - cf:write("</ul>"); + cf:write(" </ul>\n\n"); -- Write the class description: - cf:write("<a name=\"desc\"><h1>" .. a_ClassAPI.Name .. " class</h1></a>\n"); + cf:write(" <a name=\"desc\"><hr /><h1>Class " .. ClassName .. "</h1></a>\n"); if (a_ClassAPI.Desc ~= nil) then - cf:write("<p>"); - cf:write(LinkifyString(a_ClassAPI.Desc)); - cf:write("</p>\n"); + cf:write(" <p>"); + cf:write(LinkifyString(a_ClassAPI.Desc, ClassName)); + cf:write(" </p>\n\n"); end; -- Write the inheritance, if available: if (HasInheritance) then - cf:write("<a name=\"inherits\"><h1>Inheritance</h1></a>\n"); + cf:write(" <a name=\"inherits\">\n <hr /><h1>Inheritance</h1></a>\n"); if (#InheritanceChain > 0) then - cf:write("<p>This class inherits from the following parent classes:<ul>\n"); + cf:write(" <p>This class inherits from the following parent classes:</p>\n\n <ul>\n"); for i, cls in ipairs(InheritanceChain) do - cf:write("<li><a href=\"" .. cls.Name .. ".html\">" .. cls.Name .. "</a></li>\n"); + cf:write(" <li><a href=\"" .. cls.Name .. ".html\">" .. cls.Name .. "</a></li>\n"); end - cf:write("</ul></p>\n"); + cf:write(" </ul>\n\n"); end if (#a_ClassAPI.Descendants > 0) then - cf:write("<p>This class has the following descendants:\n"); + cf:write(" <p>This class has the following descendants:\n"); WriteDescendants(a_ClassAPI.Descendants); - cf:write("</p>\n"); + cf:write(" </p>\n\n"); end end -- Write the constants: - cf:write("<a name=\"constants\"><h1>Constants</h1></a>\n"); - cf:write("<table><tr><th>Name</th><th>Value</th><th>Notes</th></tr>\n"); + cf:write(" <a name=\"constants\"><hr /><h1>Constants</h1></a>\n"); + cf:write(" <table>\n <tr>\n <th>Name</th>\n <th>Value</th>\n <th>Notes</th>\n </tr>\n"); for i, cons in ipairs(a_ClassAPI.Constants) do - cf:write("<tr><td>" .. cons.Name .. "</td>"); - cf:write("<td>" .. cons.Value .. "</td>"); - cf:write("<td>" .. LinkifyString(cons.Notes or "") .. "</td></tr>\n"); + cf:write(" <tr>\n <td>" .. cons.Name .. "</td>\n"); + cf:write(" <td>" .. cons.Value .. "</td>\n"); + cf:write(" <td>" .. LinkifyString(cons.Notes or "", ClassName) .. "</td>\n </tr>\n"); end - cf:write("</table>\n"); + cf:write(" </table>\n\n"); -- Write the functions, including the inherited ones: - cf:write("<a name=\"functions\"><h1>Functions</h1></a>\n"); + cf:write(" <a name=\"functions\"><hr /><h1>Functions</h1></a>\n"); WriteFunctions(a_ClassAPI.Functions, nil); for i, cls in ipairs(InheritanceChain) do WriteFunctions(cls.Functions, cls.Name); @@ -741,12 +822,12 @@ function WriteHtmlClass(a_ClassAPI, a_AllAPI) -- Write the additional infos: if (a_ClassAPI.AdditionalInfo ~= nil) then for i, additional in ipairs(a_ClassAPI.AdditionalInfo) do - cf:write("<a name=\"additionalinfo_" .. i .. "\"><h1>" .. additional.Header .. "</h1></a>\n"); - cf:write(LinkifyString(additional.Contents)); + cf:write(" <a name=\"additionalinfo_" .. i .. "\"><h1>" .. additional.Header .. "</h1></a>\n"); + cf:write(LinkifyString(additional.Contents, ClassName)); end end - cf:write("</body></html>"); + cf:write(" </div>\n </body>\n</html>"); cf:close(); end @@ -761,18 +842,28 @@ function WriteHtmlHook(a_Hook) LOG("Cannot write \"" .. fnam .. "\": \"" .. error .. "\"."); return; end - f:write([[<html><head><title>MCServer API - ]] .. a_Hook.DefaultFnName .. [[ hook</title> - <link rel="stylesheet" type="text/css" href="main.css" /> - <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> - <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> - </head><body> - <h1>]] .. a_Hook.Name .. [[ hook</h1> - <p> - ]]); - f:write(LinkifyString(a_Hook.Desc)); - f:write("</p><h1>Callback function</h1><p>The default name for the callback function is "); - f:write(a_Hook.DefaultFnName .. ". It has the following signature:"); - f:write("<pre class=\"prettyprint lang-lua\">function " .. a_Hook.DefaultFnName .. "("); + local HookName = a_Hook.DefaultFnName; + + f:write([[<!DOCTYPE html> +<html> + <head> + <title>MCServer API - ]] .. HookName .. [[ Hook</title> + <link rel="stylesheet" type="text/css" href="main.css" /> + <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script> + <script src="http://google-code-prettify.googlecode.com/svn/trunk/src/lang-lua.js"></script> + </head> + <body> + <div id="content"> + <header> + <h1>]] .. a_Hook.Name .. [[</h1> + <hr /> + </header> + <p> +]]); + f:write(LinkifyString(a_Hook.Desc, HookName)); + f:write(" </p>\n <hr /><h1>Callback function</h1>\n <p>The default name for the callback function is "); + f:write(a_Hook.DefaultFnName .. ". It has the following signature:\n\n"); + f:write(" <pre class=\"prettyprint lang-lua\">function " .. HookName .. "("); if (a_Hook.Params == nil) then a_Hook.Params = {}; end @@ -782,22 +873,67 @@ function WriteHtmlHook(a_Hook) end f:write(param.Name); end - f:write(")</pre><p>Parameters:\n<table><tr><th>Name</th><th>Type</th><th>Notes</th></tr>\n"); + f:write(")</pre>\n\n <hr /><h1>Parameters:</h1>\n\n <table>\n <tr>\n <th>Name</th>\n <th>Type</th>\n <th>Notes</th>\n </tr>\n"); for i, param in ipairs(a_Hook.Params) do - f:write("<tr><td>" .. param.Name .. "</td><td>" .. LinkifyString(param.Type) .. "</td><td>" .. LinkifyString(param.Notes) .. "</td></tr>\n"); - end - f:write("</table></p>\n<p>" .. (a_Hook.Returns or "") .. "</p>\n"); - f:write([[<h1>Code examples</h1> - <h2>Registering the callback</h2> -<pre class=\"prettyprint lang-lua\"> -cPluginManager.AddHook(cPluginManager.]] .. a_Hook.Name .. ", My" .. a_Hook.DefaultFnName .. [[); -</pre> - ]]); + f:write(" <tr>\n <td>" .. param.Name .. "</td>\n <td>" .. LinkifyString(param.Type, HookName) .. "</td>\n <td>" .. LinkifyString(param.Notes, HookName) .. "</td>\n </tr>\n"); + end + f:write(" </table>\n\n <p>" .. (a_Hook.Returns or "") .. "</p>\n\n"); + f:write([[ <hr /><h1>Code examples</h1> + <h2>Registering the callback</h2> + +]]); + f:write(" <pre class=\"prettyprint lang-lua\">\n"); + f:write([[cPluginManager.AddHook(cPluginManager.]] .. a_Hook.Name .. ", My" .. a_Hook.DefaultFnName .. [[);]]); + f:write("</pre>\n\n"); local Examples = a_Hook.CodeExamples or {}; for i, example in ipairs(Examples) do - f:write("<h2>" .. example.Title .. "</h2>\n"); - f:write("<p>" .. example.Desc .. "</p>\n"); - f:write("<pre class=\"prettyprint lang-lua\">" .. example.Code .. "</pre>\n"); + f:write(" <h2>" .. (example.Title or "<i>missing Title</i>") .. "</h2>\n"); + f:write(" <p>" .. (example.Desc or "<i>missing Desc</i>") .. "</p>\n\n"); + f:write(" <pre class=\"prettyprint lang-lua\">" .. (example.Code or "<i>missing Code</i>") .. "\n </pre>\n\n"); + end + f:write([[ </div> + </body> +</html>]]); + f:close(); +end + + + + + +function ListMissingPages() + local MissingPages = {}; + for PageName, Referrers in pairs(g_TrackedPages) do + if not(cFile:Exists("API/" .. PageName .. ".html")) then + table.insert(MissingPages, {Name = PageName, Refs = Referrers} ); + end + end; + g_TrackedPages = {}; + + if (#MissingPages == 0) then + -- No missing pages, congratulations! + return; + end + + -- Sort the pages by name: + table.sort(MissingPages, + function (Page1, Page2) + return (Page1.Name < Page2.Name); + end + ); + + -- Output the pages: + local f, err = io.open("API/_missingPages.txt", "w"); + if (f == nil) then + LOGWARNING("Cannot open _missingPages.txt for writing: '" .. err .. "'. There are " .. #MissingPages .. " pages missing."); + return; + end + for idx, pg in ipairs(MissingPages) do + f:write(pg.Name .. ":\n"); + -- Sort and output the referrers: + table.sort(pg.Refs); + f:write("\t" .. table.concat(pg.Refs, "\n\t")); + f:write("\n\n"); end f:close(); end |