summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Sehmisch <mana@crushnet.org>2011-04-27 15:24:36 +0200
committerPhilipp Sehmisch <mana@crushnet.org>2011-04-27 15:24:36 +0200
commitc1a6e9947231cc511016378bd326a5d64b0aa18c (patch)
tree03c9b42ae97d61b5568f27ddc3f1be7a8b6648c3
parentca1dd6fcaf1dfaef0f18df0b6c114f1baa25d2ce (diff)
downloadmanaserv-c1a6e9947231cc511016378bd326a5d64b0aa18c.tar.gz
manaserv-c1a6e9947231cc511016378bd326a5d64b0aa18c.tar.xz
manaserv-c1a6e9947231cc511016378bd326a5d64b0aa18c.zip
Added a simple crafting system
A client can craft something using the @craft command. The command needs a list of item names and amounts. The gameserver checks if the character has these items in the inventory and then passes the list together with the character handle to the lua script function on_craft in the script file scripts/crafting.lua. This function can then be used to evaluate if the list is a valid crafting combination and when this is the case take or give items. Implemented two example crafting scripts there, one which enforces exact item order and amount and one which doesn't. Both are disabled per default and one needs to be enabled by uncommenting a line. Also gave the player group permission to use the @craft command in permissions.xml and added two new items (wood and iron) required for the example crafting combination. Resolves: #333 Reviewed-by: bcs86, Bertram
-rw-r--r--example/clientdata/graphics/items/crafting/generic-ingot.pngbin0 -> 1615 bytes
-rw-r--r--example/clientdata/graphics/items/crafting/generic-rawlog.pngbin0 -> 1719 bytes
-rw-r--r--example/clientdata/items.xml10
-rw-r--r--example/serverdata/permissions.xml1
-rw-r--r--example/serverdata/scripts/crafting.lua99
-rw-r--r--src/game-server/commandhandler.cpp78
-rw-r--r--src/game-server/main-game.cpp2
-rw-r--r--src/scripting/luascript.cpp38
-rw-r--r--src/scripting/luascript.h3
-rw-r--r--src/scripting/script.cpp14
-rw-r--r--src/scripting/script.h8
11 files changed, 253 insertions, 0 deletions
diff --git a/example/clientdata/graphics/items/crafting/generic-ingot.png b/example/clientdata/graphics/items/crafting/generic-ingot.png
new file mode 100644
index 0000000..0dc7b1e
--- /dev/null
+++ b/example/clientdata/graphics/items/crafting/generic-ingot.png
Binary files differ
diff --git a/example/clientdata/graphics/items/crafting/generic-rawlog.png b/example/clientdata/graphics/items/crafting/generic-rawlog.png
new file mode 100644
index 0000000..038d5a5
--- /dev/null
+++ b/example/clientdata/graphics/items/crafting/generic-rawlog.png
Binary files differ
diff --git a/example/clientdata/items.xml b/example/clientdata/items.xml
index ff0b1c5..6f72185 100644
--- a/example/clientdata/items.xml
+++ b/example/clientdata/items.xml
@@ -160,4 +160,14 @@
<sprite gender="male">equipment/chest/chest-leather-male.xml</sprite>
<sprite gender="female">equipment/chest/chest-leather-female.xml</sprite>
</item>
+
+ <!-- example crafting ingredients -->
+ <item id="8" max-per-slot="99" name="Iron"
+ description="Combine with one wood to create a sword"
+ image="crafting/generic-ingot.png"
+ value="1" />
+ <item id="9" max-per-slot="99" name="Wood"
+ description="Combine with two iron to create a sword"
+ image="crafting/generic-rawlog.png"
+ value="1" />
</items>
diff --git a/example/serverdata/permissions.xml b/example/serverdata/permissions.xml
index 91abd84..c07bfae 100644
--- a/example/serverdata/permissions.xml
+++ b/example/serverdata/permissions.xml
@@ -6,6 +6,7 @@
<allow>@where</allow>
<allow>@rights</allow>
<allow>@report</allow>
+ <allow>@craft</allow>
</class>
<class level="2">
<alias>tester</alias>
diff --git a/example/serverdata/scripts/crafting.lua b/example/serverdata/scripts/crafting.lua
new file mode 100644
index 0000000..e893982
--- /dev/null
+++ b/example/serverdata/scripts/crafting.lua
@@ -0,0 +1,99 @@
+-------------------------------------------------------------
+-- Example crafting script file --
+-- --
+-- This file allows you to implement your own crafting --
+-- system. --
+----------------------------------------------------------------------------------
+-- Copyright 2011 Manasource Development Team --
+-- --
+-- This file is part of Manasource. --
+-- --
+-- Manasource is free software; you can redistribute it and/or modify it --
+-- under the terms of the GNU General Public License as published by the Free --
+-- Software Foundation; either version 2 of the License, or any later version. --
+----------------------------------------------------------------------------------
+
+-- This function is called by the game engine when a character tries to craft
+-- something from items in its inventory
+function on_craft(ch, recipe)
+ -- ch is the crafting character
+ --
+ -- recipe is a table with the ingredients.
+ -- it is a common 1-based array. each element of this array is a table with the
+ -- two keys "id" and "amount".
+ -- The engine has already checked that the character owns enough of those things,
+ -- so you needn't do this again.
+
+ -- uncomment one (but not both!) of the following three lines to enable the
+ -- example crafting systems
+
+ mana.chatmessage(ch, "There is no crafting in this game world.")
+ --craft_strict(ch, recipe)
+ --craft_lax(ch, recipe)
+end
+
+
+-- a primitive example crafting system which cares about item order and exact amount
+function craft_strict(ch, recipe)
+ if (recipe[1].id == 8 and recipe[1].amount == 2 and -- has two iron
+ recipe[2].id == 9 and recipe[2].amount == 1) -- and one wood
+ then
+ mana.chr_inv_change(ch,
+ 8, -2, --take away the iron
+ 9, -1, --take away the wood
+ 5, 1 ) -- give a sword
+ return
+ mana.chatmessage(ch, "You've crafted a sword")
+ end
+ mana.chatmessage(ch, "This wouldn't create anything useful")
+end
+
+-- a primitive example crafting system which doesn't care about item order
+-- and amount. It even allows to mention the same item multiple times.
+function craft_lax(ch, recipe)
+ recipe = make_condensed_and_sorted_item_list(recipe)
+
+ if (recipe[1].id == 8 and recipe[1].amount >= 2 and -- has at least two iron
+ recipe[2].id == 9 and recipe[2].amount >= 1) -- and at least one wood
+ then
+ mana.chr_inv_change(ch,
+ 8, -2, --take away the iron
+ 9, -1, --take away the wood
+ 5, 1 ) -- give a sword
+ return
+ mana.chatmessage(ch, "You've crafted a sword")
+ end
+ mana.chatmessage(ch, "This wouldn't create anything useful")
+end
+
+-- this turns multiple occurences of the same item into one by adding up
+-- their amounts and sorts the recipe by item ID.
+-- This makes stuff a lot easier when your crafting system isn't supposed to care
+-- about the order items are in.
+function make_condensed_and_sorted_item_list(recipe)
+
+ local condensed = {}
+ for index, item in pairs(recipe) do
+ if condensed[item.id] == nil then
+ condensed[item.id] = item.amount
+ else
+ condensed[item.id] = condensed[item.id] + item.amount
+ end
+ end
+
+ local sorted = {}
+ for id, amount in pairs(condensed) do
+ local item = {}
+ item.id = id
+ item.amount = amount
+ table.insert(sorted, item)
+ end
+
+ table.sort(sorted, function(item1, item2)
+ return (item1.id < item2.id)
+ end
+ )
+
+ return sorted
+
+end \ No newline at end of file
diff --git a/src/game-server/commandhandler.cpp b/src/game-server/commandhandler.cpp
index d7bd8f4..f0cbcf3 100644
--- a/src/game-server/commandhandler.cpp
+++ b/src/game-server/commandhandler.cpp
@@ -33,6 +33,8 @@
#include "game-server/monstermanager.h"
#include "game-server/state.h"
+#include "scripting/script.h"
+
#include "common/configuration.h"
#include "common/permissionmanager.h"
#include "common/transaction.h"
@@ -73,6 +75,7 @@ static void handleKick(Character*, std::string&);
static void handleLog(Character*, std::string&);
static void handleLogsay(Character*, std::string&);
static void handleKillMonsters(Character*, std::string&);
+static void handleCraft(Character*, std::string&);
static CmdRef const cmdRef[] =
{
@@ -128,6 +131,8 @@ static CmdRef const cmdRef[] =
"Says something in public chat while logging it to the transaction log.", &handleLogsay},
{"killmonsters", "",
"Kills all monsters on the map.", &handleKillMonsters},
+ {"craft", "{ <item> <amount> }",
+ "Crafts something.", &handleCraft},
{NULL, NULL, NULL, NULL}
};
@@ -1291,6 +1296,79 @@ static void handleKillMonsters(Character *player, std::string &args)
TRANS_CMD_KILLMONSTERS, msg);
}
+static void handleCraft(Character *player, std::string &args)
+{
+ std::stringstream errMsg;
+ std::list<InventoryItem> recipe;
+ Inventory playerInventory(player);
+ std::map<int, int> totalAmountOfItem;
+
+ while (true)
+ {
+ // parsing
+ std::string strItem = getArgument(args);
+ ItemClass* item = itemManager->getItemByName(strItem);
+ std::string strAmount = getArgument(args);
+ int amount = utils::stringToInt(strAmount);
+
+ // syntax error checking
+ if (strItem.empty())
+ {
+ // the item list has ended
+ break;
+ }
+ if (!item)
+ {
+ // item wasn't found in the item database
+ errMsg << "Unknown item: \"" << strItem << "\".";
+ break;
+ }
+
+ if (strAmount.empty())
+ {
+ // the last item in the list has no amount defined
+ errMsg << "No amount given for \"" << strItem << "\".";
+ break;
+ }
+ if (amount < 1)
+ {
+ errMsg << "Illegal amount \""<< strAmount << "\" for item \"" << strItem << "\".";
+ break;
+ }
+
+ // inventory checking
+ int available = playerInventory.count(item->getDatabaseID());
+ if (available == 0)
+ {
+ errMsg << "You have no "<< strItem << " in your inventory.";
+ break;
+ }
+ if (available < amount)
+ {
+ errMsg << "You haven't got that many "<< strItem << "s in your inventory.";
+ break;
+ }
+
+ // when there is still no break, add the item;
+ InventoryItem recipeItem;
+ recipeItem.itemId = item->getDatabaseID();
+ recipeItem.amount = amount;
+ recipe.push_back(recipeItem);
+ }
+
+ if (!errMsg.str().empty())
+ {
+ // when an error occured, output the error
+ say(errMsg.str(), player);
+ return;
+ } else {
+ // pass to script engine. The engine is responsible for all
+ // further processing of the crafting operation, including
+ // outputting an error message when the recipe is invalid.
+ Script::performCraft(player, recipe);
+ }
+}
+
void CommandHandler::handleCommand(Character *player,
const std::string &command)
{
diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp
index b54e382..63ea004 100644
--- a/src/game-server/main-game.cpp
+++ b/src/game-server/main-game.cpp
@@ -74,6 +74,7 @@ using utils::Logger;
#define DEFAULT_PERMISSION_FILE "permissions.xml"
#define DEFAULT_GLOBAL_EVENT_SCRIPT_FILE "scripts/global_events.lua"
#define DEFAULT_SPECIAL_ACTIONS_SCRIPT_FILE "scripts/special_actions.lua"
+#define DEFAULT_CRAFT_SCRIPT_FILE "scripts/crafting.lua"
static int const WORLD_TICK_SKIP = 2; /** tolerance for lagging behind in world calculation) **/
@@ -200,6 +201,7 @@ static void initializeServer()
LuaScript::loadGlobalEventScript(DEFAULT_GLOBAL_EVENT_SCRIPT_FILE);
LuaScript::loadSpecialActionsScript(DEFAULT_SPECIAL_ACTIONS_SCRIPT_FILE);
+ LuaScript::loadCraftScript(DEFAULT_CRAFT_SCRIPT_FILE);
// --- Initialize the global handlers
// FIXME: Make the global handlers global vars or part of a bigger
diff --git a/src/scripting/luascript.cpp b/src/scripting/luascript.cpp
index a9c43b7..dc6230c 100644
--- a/src/scripting/luascript.cpp
+++ b/src/scripting/luascript.cpp
@@ -21,6 +21,9 @@
#include "luascript.h"
+
+#include "scripting/luautil.h"
+
#include "game-server/being.h"
#include "utils/logger.h"
@@ -61,6 +64,30 @@ void LuaScript::push(Thing *v)
++nbArgs;
}
+void LuaScript::push(const std::list<InventoryItem> &itemList)
+{
+ assert(nbArgs >= 0);
+ int position = 0;
+
+ lua_createtable(mState, itemList.size(), 0);
+ int itemTable = lua_gettop(mState);
+
+ for (std::list<InventoryItem>::const_iterator i = itemList.begin();
+ i != itemList.end();
+ i++)
+ {
+ // create the item structure
+ std::map<std::string, int> item;
+ item["id"] = i->itemId;
+ item["amount"] = i->amount;
+ // add the item structure to the item table under the next index
+ lua_pushinteger(mState, ++position);
+ pushSTLContainer<std::string, int>(mState, item);
+ lua_settable(mState, itemTable);
+ }
+ ++nbArgs;
+}
+
int LuaScript::execute()
{
assert(nbArgs >= 0);
@@ -182,3 +209,14 @@ bool LuaScript::loadSpecialActionsScript(const std::string &file)
}
return true;
}
+
+bool LuaScript::loadCraftScript(const std::string &file)
+{
+ Script::craftScript = new LuaScript();
+ if (!Script::craftScript->loadFile(file))
+ {
+ Script::craftScript = NULL;
+ return false;
+ }
+ return true;
+}
diff --git a/src/scripting/luascript.h b/src/scripting/luascript.h
index af13aa2..b9bde2d 100644
--- a/src/scripting/luascript.h
+++ b/src/scripting/luascript.h
@@ -52,6 +52,8 @@ class LuaScript: public Script
void push(Thing *);
+ void push(const std::list<InventoryItem> &itemList);
+
int execute();
static void getQuestCallback(Character *, const std::string &,
@@ -69,6 +71,7 @@ class LuaScript: public Script
*/
static bool loadGlobalEventScript(const std::string &file);
static bool loadSpecialActionsScript(const std::string &file);
+ static bool loadCraftScript(const std::string &file);
private:
lua_State *mState;
diff --git a/src/scripting/script.cpp b/src/scripting/script.cpp
index b222b0f..490abf0 100644
--- a/src/scripting/script.cpp
+++ b/src/scripting/script.cpp
@@ -34,6 +34,7 @@ typedef std::map< std::string, Script::Factory > Engines;
static Engines *engines = NULL;
Script *Script::globalEventScript = NULL;
Script *Script::specialActionsScript = NULL;
+Script *Script::craftScript = NULL;
Script::Script():
mMap(NULL),
@@ -158,3 +159,16 @@ bool Script::performSpecialAction(int specialId, Being* caster)
}
return true;
}
+
+bool Script::performCraft(Being* crafter, std::list<InventoryItem> recipe)
+{
+ Script *script = Script::craftScript;
+ if (script)
+ {
+ script->prepare("on_craft");
+ script->push(crafter);
+ script->push(recipe);
+ script->execute();
+ }
+ return true;
+}
diff --git a/src/scripting/script.h b/src/scripting/script.h
index 43eebe1..44a8b7a 100644
--- a/src/scripting/script.h
+++ b/src/scripting/script.h
@@ -105,6 +105,12 @@ class Script
virtual void push(Thing *) = 0;
/**
+ * Pushes a list of items with amounts to the
+ * script engine.
+ */
+ virtual void push(const std::list<InventoryItem> &itemList) = 0;
+
+ /**
* Executes the function being prepared.
* @return the value returned by the script.
*/
@@ -135,11 +141,13 @@ class Script
static bool executeGlobalEventFunction(const std::string &function, Being *obj);
static void addDataToSpecial(int specialId, Special *special);
static bool performSpecialAction(int specialId, Being *caster);
+ static bool performCraft(Being* crafter, std::list<InventoryItem> recipe);
protected:
static Script *globalEventScript;
static Script *specialActionsScript;
+ static Script *craftScript;
std::string mScriptFile;
private: