Files
mod-ale/LuaEngine.h
Rochet2 e131f36d39 Eluna add new system for userdata management. single userdata with GC.
Note that this will still need the core side destructors to have the ref remove function used in order to have invalid pointer errors.
2014-06-29 21:28:48 +02:00

810 lines
25 KiB
C++

/*
* Copyright (C) 2010 - 2014 Eluna Lua Engine <http://emudevs.com/>
* This program is free software licensed under GPL version 3
* Please see the included DOCS/LICENSE.md for more information
*/
#ifndef __ELUNA__H
#define __ELUNA__H
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
};
#include "HookMgr.h"
// Required
#include "AccountMgr.h"
#include "AuctionHouseMgr.h"
#include "Cell.h"
#include "CellImpl.h"
#include "Chat.h"
#include "Channel.h"
#ifdef MANGOS
#include "Config/Config.h"
#else
#include "Config.h"
#endif
#include "DBCStores.h"
#include "GossipDef.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "Group.h"
#include "Guild.h"
#include "GuildMgr.h"
#include "Language.h"
#include "Mail.h"
#include "MapManager.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Player.h"
#include "Pet.h"
#include "ReputationMgr.h"
#include "revision.h"
#include "ScriptMgr.h"
#include "Spell.h"
#include "SpellAuras.h"
#include "SpellMgr.h"
#include "TemporarySummon.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#ifdef MANGOS
#include "ReactorAI.h"
#include "revision_nr.h"
#else
#include "ScriptedCreature.h"
#include "SpellInfo.h"
#include "WeatherMgr.h"
#endif
#if (!defined(TBC) && !defined(CLASSIC))
#include "Vehicle.h"
#endif
#ifndef CLASSIC
#include "ArenaTeam.h"
#endif
typedef std::set<std::string> ScriptPaths;
#ifdef MANGOS
#undef sWorld
#undef sMapMgr
#undef sConfigMgr
#undef sGuildMgr
#undef sObjectMgr
#undef sAccountMgr
#undef sObjectAccessor
#define sWorld (&MaNGOS::Singleton<World>::Instance())
#define sMapMgr (&MapManager::Instance())
#define sConfigMgr (&MaNGOS::Singleton<Config>::Instance())
#define sGuildMgr (&MaNGOS::Singleton<GuildMgr>::Instance())
#define sObjectMgr (&MaNGOS::Singleton<ObjectMgr>::Instance())
#define sAccountMgr (&MaNGOS::Singleton<AccountMgr>::Instance())
#define sObjectAccessor (&ObjectAccessor::Instance())
#define MAKE_NEW_GUID(l, e, h) ObjectGuid(h, e, l)
#define GET_GUID GetObjectGuid
#define GetGameObjectTemplate GetGameObjectInfo
#define GetItemTemplate GetItemPrototype
#define ELUNA_LOG_INFO(...) sLog.outString(__VA_ARGS__);
#define ELUNA_LOG_ERROR(...) sLog.outErrorEluna(__VA_ARGS__);
#define ELUNA_LOG_DEBUG(...) sLog.outDebug(__VA_ARGS__);
#define CORE_VERSION REVISION_NR
#define CORE_NAME "MaNGOS"
#define SERVER_MSG_STRING SERVER_MSG_CUSTOM
#define MAX_LOCALES MAX_LOCALE
#define DIALOG_STATUS_SCRIPTED_NO_STATUS DIALOG_STATUS_UNDEFINED
#define TARGETICONCOUNT TARGET_ICON_COUNT
typedef TemporarySummon TempSummon;
#ifndef CLASSIC
#define PLAYER_FIELD_LIFETIME_HONORABLE_KILLS PLAYER_FIELD_LIFETIME_HONORBALE_KILLS
#endif
#define MAX_TALENT_SPECS MAX_TALENT_SPEC_COUNT
#define GUID_ENPART(guid) ObjectGuid(guid).GetEntry()
#define GUID_LOPART(guid) ObjectGuid(guid).GetCounter()
#define GUID_HIPART(guid) ObjectGuid(guid).GetHigh()
enum SelectAggroTarget
{
SELECT_TARGET_RANDOM = 0, // Just selects a random target
SELECT_TARGET_TOPAGGRO, // Selects targes from top aggro to bottom
SELECT_TARGET_BOTTOMAGGRO, // Selects targets from bottom aggro to top
SELECT_TARGET_NEAREST,
SELECT_TARGET_FARTHEST
};
#ifdef TBC
#define SPELL_AURA_MOD_KILL_XP_PCT SPELL_AURA_MOD_XP_PCT
#endif
#ifdef CLASSIC
#undef Opcodes
#define Opcodes OpcodesList
#endif
#else
#ifndef CATA
typedef uint64 ObjectGuid;
#endif
#define GET_GUID GetGUID
#define CORE_VERSION _DATE
#define CORE_NAME "TrinityCore"
#define REGEN_TIME_FULL
#define ELUNA_LOG_INFO(...) TC_LOG_INFO("eluna", __VA_ARGS__);
#define ELUNA_LOG_ERROR(...) TC_LOG_ERROR("eluna", __VA_ARGS__);
#define ELUNA_LOG_DEBUG(...) TC_LOG_DEBUG("eluna", __VA_ARGS__);
typedef ThreatContainer::StorageType ThreatList;
#ifdef CATA
#define NUM_MSG_TYPES NUM_OPCODE_HANDLERS
#endif
#endif
template<typename T>
struct ElunaRegister
{
const char* name;
int(*mfunc)(lua_State*, T*);
};
struct EventMgr
{
struct LuaEvent;
typedef std::set<LuaEvent*> EventSet;
typedef std::map<EventProcessor*, EventSet> EventMap;
// typedef UNORDERED_MAP<uint64, EventProcessor> ProcessorMap;
EventMap LuaEvents; // LuaEvents[processor] = {LuaEvent, LuaEvent...}
// ProcessorMap Processors; // Processors[guid] = processor
EventProcessor GlobalEvents;
struct LuaEvent : public BasicEvent
{
LuaEvent(EventProcessor* _events, int _funcRef, uint32 _delay, uint32 _calls, Object* _obj);
~LuaEvent();
// Should never execute on dead events
bool Execute(uint64 time, uint32 diff);
EventProcessor* events; // Pointer to events (holds the timed event)
int funcRef; // Lua function reference ID, also used as event ID
uint32 delay; // Delay between event calls
uint32 calls; // Amount of calls to make, 0 for infinite
Object* obj; // Object to push
};
// Should be run on world tick
void Update(uint32 diff)
{
GlobalEvents.Update(diff);
}
// Updates processor stored for guid || remove from Update()
// Should be run on gameobject tick
/*void Update(uint64 guid, uint32 diff)
{
if (Processors.find(guid) == Processors.end())
return;
Processors[guid].Update(diff);
}*/
// Aborts all lua events
void KillAllEvents(EventProcessor* events)
{
if (!events)
return;
if (LuaEvents.empty())
return;
EventMap::const_iterator it = LuaEvents.find(events); // Get event set
if (it == LuaEvents.end())
return;
if (it->second.empty())
return;
for (EventSet::const_iterator itr = it->second.begin(); itr != it->second.end();) // Loop events
(*(itr++))->to_Abort = true; // Abort event
}
// Remove all timed events
void RemoveEvents()
{
if (!LuaEvents.empty())
for (EventMap::const_iterator it = LuaEvents.begin(); it != LuaEvents.end();) // loop processors
KillAllEvents((it++)->first);
LuaEvents.clear(); // remove pointers
// This is handled automatically on delete
// for (ProcessorMap::iterator it = Processors.begin(); it != Processors.end();)
// (it++)->second.KillAllEvents(true);
// Processors.clear(); // remove guid saved processors
GlobalEvents.KillAllEvents(true);
}
// Remove timed events from processor
void RemoveEvents(EventProcessor* events)
{
if (!events)
return;
KillAllEvents(events);
LuaEvents.erase(events); // remove pointer set
}
// Remove timed events from guid
// void RemoveEvents(uint64 guid)
//{
// if (Processors.empty())
// return;
// if (Processors.find(guid) != Processors.end())
// LuaEvents.erase(&Processors[guid]);
// // Processors[guid].KillAllEvents(true); // remove events
// Processors.erase(guid); // remove processor
//}
// Adds a new event to the processor and returns the eventID or 0 (Never negative)
int AddEvent(EventProcessor* events, int funcRef, uint32 delay, uint32 calls, Object* obj = NULL)
{
if (!events || funcRef <= 0) // If funcRef <= 0, function reference failed
return 0; // on fail always return 0. funcRef can be negative.
events->AddEvent(new LuaEvent(events, funcRef, delay, calls, obj), events->CalculateTime(delay));
return funcRef; // return the event ID
}
// Creates a processor for the guid if needed and adds the event to it
// int AddEvent(uint64 guid, int funcRef, uint32 delay, uint32 calls, Object* obj = NULL)
//{
// if (!guid) // 0 should be unused
// return 0;
// return AddEvent(&Processors[guid], funcRef, delay, calls, obj);
//}
// Finds the event that has the ID from events
LuaEvent* GetEvent(EventProcessor* events, int eventId)
{
if (!events || !eventId)
return NULL;
if (LuaEvents.empty())
return NULL;
EventMap::const_iterator it = LuaEvents.find(events); // Get event set
if (it == LuaEvents.end())
return NULL;
if (it->second.empty())
return NULL;
for (EventSet::const_iterator itr = it->second.begin(); itr != it->second.end(); ++itr) // Loop events
if ((*itr) && (*itr)->funcRef == eventId) // Check if the event has our ID
return *itr; // Return the event if found
return NULL;
}
// Remove the event with the eventId from processor
// Returns true if event is removed
bool RemoveEvent(EventProcessor* events, int eventId) // eventId = funcRef
{
if (!events || !eventId)
return false;
LuaEvent* luaEvent = GetEvent(events, eventId);
if (!luaEvent)
return false;
luaEvent->to_Abort = true; // Set to remove on next call
LuaEvents[events].erase(luaEvent); // Remove pointer
return true;
}
// Remove event by ID from processor stored for guid
/*bool RemoveEvent(uint64 guid, int eventId)
{
if (Processors.empty())
return false;
if (!guid || Processors.find(guid) == Processors.end())
return false;
return RemoveEvent(&Processors[guid], eventId);
}*/
// Removes the eventId from all events
void RemoveEvent(int eventId)
{
if (!eventId)
return;
if (LuaEvents.empty())
return;
for (EventMap::const_iterator it = LuaEvents.begin(); it != LuaEvents.end();) // loop processors
if (RemoveEvent((it++)->first, eventId))
break; // succesfully remove the event, stop loop.
}
~EventMgr()
{
RemoveEvents();
}
};
class Eluna
{
public:
friend class ScriptMgr;
// friend class ACE_Singleton<Eluna, ACE_Null_Mutex>;
lua_State* L;
EventMgr m_EventMgr;
// ACE_Recursive_Thread_Mutex lock;
Eluna()
{
L = NULL;
}
~Eluna()
{
}
struct EventBind
{
typedef std::vector<int> ElunaBindingMap;
typedef std::map<int, ElunaBindingMap> ElunaEntryMap;
~EventBind()
{
Clear();
}
void Clear(); // unregisters all registered functions and clears all registered events from the bind std::maps (reset)
void Insert(int eventId, int funcRef); // Inserts a new registered event
// Gets the binding std::map containing all registered events with the function refs for the entry
ElunaBindingMap* GetBindMap(int eventId)
{
if (Bindings.empty())
return NULL;
ElunaEntryMap::iterator itr = Bindings.find(eventId);
if (itr == Bindings.end())
return NULL;
return &itr->second;
}
// Checks if there are events for ID
bool HasEvents(int eventId) const;
// Cleans stack and pushes eventId
void BeginCall(int eventId) const;
// Loops through all registered events for the eventId at stack index 1
// Copies the whole stack as arguments for the called function. Before Executing, push all params to stack!
// Leaves return values from all functions in order to the stack.
void ExecuteCall();
void EndCall() const;
ElunaEntryMap Bindings; // Binding store Bindings[eventId] = {funcRef};
};
struct EntryBind
{
typedef std::map<int, int> ElunaBindingMap;
typedef UNORDERED_MAP<uint32, ElunaBindingMap> ElunaEntryMap;
~EntryBind()
{
Clear();
}
void Clear(); // unregisters all registered functions and clears all registered events from the bind std::maps (reset)
void Insert(uint32 entryId, int eventId, int funcRef); // Inserts a new registered event
// Gets the function ref of an entry for an event
int GetBind(uint32 entryId, int eventId) const
{
if (Bindings.empty())
return 0;
ElunaEntryMap::const_iterator itr = Bindings.find(entryId);
if (itr == Bindings.end() || itr->second.empty())
return 0;
ElunaBindingMap::const_iterator itr2 = itr->second.find(eventId);
if (itr2 == itr->second.end())
return 0;
return itr2->second;
}
// Gets the binding std::map containing all registered events with the function refs for the entry
const ElunaBindingMap* GetBindMap(uint32 entryId) const
{
if (Bindings.empty())
return NULL;
ElunaEntryMap::const_iterator itr = Bindings.find(entryId);
if (itr == Bindings.end())
return NULL;
return &itr->second;
}
// Returns true if the entry has registered binds
bool HasBinds(uint32 entryId) const
{
if (Bindings.empty())
return false;
return Bindings.find(entryId) != Bindings.end();
}
ElunaEntryMap Bindings; // Binding store Bindings[entryId][eventId] = funcRef;
};
// Use templates for EventBind
EventBind PacketEventBindings;
EventBind ServerEventBindings;
EventBind PlayerEventBindings;
EventBind GuildEventBindings;
EventBind GroupEventBindings;
EventBind VehicleEventBindings;
EntryBind CreatureEventBindings;
EntryBind CreatureGossipBindings;
EntryBind GameObjectEventBindings;
EntryBind GameObjectGossipBindings;
EntryBind ItemEventBindings;
EntryBind ItemGossipBindings;
EntryBind playerGossipBindings;
static void report(lua_State*);
void Register(uint8 reg, uint32 id, uint32 evt, int func);
void BeginCall(int fReference);
bool ExecuteCall(int params, int res);
void EndCall(int res);
void GetScripts(std::string path, ScriptPaths& scripts);
void RunScripts(ScriptPaths& scripts);
// Pushes
void Push(lua_State*); // nil
void Push(lua_State*, const uint64);
void Push(lua_State*, const int64);
void Push(lua_State*, const uint32);
void Push(lua_State*, const int32);
void Push(lua_State*, const bool);
void Push(lua_State*, const float);
void Push(lua_State*, const double);
void Push(lua_State*, const char*);
void Push(lua_State*, const std::string);
template<typename T> void Push(lua_State* L, T const* ptr)
{
ElunaTemplate<T>::push(L, ptr);
}
void Push(lua_State* L, Object const* obj);
void Push(lua_State* L, WorldObject const* obj);
void Push(lua_State* L, Unit const* unit);
void Push(lua_State* L, Pet const* pet);
void Push(lua_State* L, TempSummon const* summon);
// Checks
template<typename T> T CHECKVAL(lua_State* L, int narg);
template<typename T> T CHECKVAL(lua_State* L, int narg, T def);
template<typename T> T* CHECKOBJ(lua_State* L, int narg, bool error = true)
{
return ElunaTemplate<T>::check(L, narg, error);
}
struct ObjectGUIDCheck
{
ObjectGUIDCheck(ObjectGuid guid): _guid(guid) {}
bool operator()(WorldObject* object)
{
return object->GET_GUID() == _guid;
}
ObjectGuid _guid;
};
// Binary predicate to sort WorldObjects based on the distance to a reference WorldObject
struct ObjectDistanceOrderPred
{
ObjectDistanceOrderPred(WorldObject const* pRefObj, bool ascending = true): m_refObj(pRefObj), m_ascending(ascending) {}
bool operator()(WorldObject const* pLeft, WorldObject const* pRight) const
{
return m_ascending ? m_refObj->GetDistanceOrder(pLeft, pRight) : !m_refObj->GetDistanceOrder(pLeft, pRight);
}
WorldObject const* m_refObj;
const bool m_ascending;
};
// Doesn't get self
struct WorldObjectInRangeCheck
{
WorldObjectInRangeCheck(bool nearest, WorldObject const* obj, float range,
uint16 typeMask = 0, uint32 entry = 0, uint32 hostile = 0): i_nearest(nearest),
i_obj(obj), i_range(range), i_typeMask(typeMask), i_entry(entry), i_hostile(hostile)
{
}
WorldObject const& GetFocusObject() const { return *i_obj; }
bool operator()(WorldObject* u)
{
if (i_typeMask && !u->isType(TypeMask(i_typeMask)))
return false;
if (i_entry && u->GetEntry() != i_entry)
return false;
if (i_obj->GET_GUID() == u->GET_GUID())
return false;
if (!i_obj->IsWithinDistInMap(u, i_range))
return false;
if (Unit* unit = u->ToUnit())
{
#ifdef MANGOS
if (!unit->isAlive())
return false;
#else
if (!unit->IsAlive())
return false;
#endif
if (i_hostile)
{
if (const Unit* obj = i_obj->ToUnit())
{
if ((i_hostile == 1) != obj->IsHostileTo(unit))
return false;
}
}
}
if (i_nearest)
i_range = i_obj->GetDistance(u);
return true;
}
WorldObject const* i_obj;
float i_range;
uint16 i_typeMask;
uint32 i_entry;
bool i_nearest;
uint32 i_hostile;
WorldObjectInRangeCheck(WorldObjectInRangeCheck const&);
};
};
template<> Unit* Eluna::CHECKOBJ<Unit>(lua_State* L, int narg, bool error);
template<> Player* Eluna::CHECKOBJ<Player>(lua_State* L, int narg, bool error);
template<> Creature* Eluna::CHECKOBJ<Creature>(lua_State* L, int narg, bool error);
template<> GameObject* Eluna::CHECKOBJ<GameObject>(lua_State* L, int narg, bool error);
template<> Corpse* Eluna::CHECKOBJ<Corpse>(lua_State* L, int narg, bool error);
#ifdef MANGOS
#define sEluna (&MaNGOS::Singleton<Eluna>::Instance())
#else
#define sEluna ACE_Singleton<Eluna, ACE_Null_Mutex>::instance()
#endif
#define ELUNA_GUARD() // ACE_Guard< ACE_Recursive_Thread_Mutex > ELUNA_GUARD_OBJECT(sEluna->lock);
template<typename T>
class ElunaTemplate
{
public:
static const char* tname;
static bool manageMemory;
static int typeT(lua_State* L)
{
lua_pushstring(L, tname);
return 1;
}
// name will be used as type name
// If gc is true, lua will handle the memory management for object pushed
// gc should be used if pushing for example WorldPacket,
// that will only be needed on lua side and will not be managed by TC/mangos/<core>
static void Register(lua_State* L, const char* name, bool gc = false)
{
tname = name;
manageMemory = gc;
lua_newtable(L);
int methods = lua_gettop(L);
// store method table in globals so that
// scripts can add functions in Lua
lua_pushvalue(L, methods);
lua_setglobal(L, tname);
luaL_newmetatable(L, tname);
int metatable = lua_gettop(L);
// hide metatable
lua_pushvalue(L, methods);
lua_setfield(L, metatable, "__metatable");
// required to access methods
lua_pushvalue(L, methods);
lua_setfield(L, metatable, "__index");
// metamethods
lua_pushcfunction(L, tostringT);
lua_setfield(L, metatable, "__tostring");
lua_pushcfunction(L, gcT);
lua_setfield(L, metatable, "__gc");
// special method to get the object type
lua_pushcfunction(L, typeT);
lua_setfield(L, methods, "GetObjectType");
lua_setmetatable(L, methods);
lua_remove(L, methods);
}
template<typename C>
static void SetMethods(lua_State* L, ElunaRegister<C>* methodTable)
{
if (!methodTable)
return;
luaL_getmetatable(L, tname);
if (!lua_istable(L, -1))
{
lua_remove(L, -1);
ELUNA_LOG_ERROR("%s missing metatable", tname);
return;
}
lua_getfield(L, -1, "__metatable");
lua_remove(L, -2);
if (!lua_istable(L, -1))
{
lua_remove(L, -1);
ELUNA_LOG_ERROR("%s missing method table from metatable", tname);
return;
}
for (; methodTable && methodTable->name && methodTable->mfunc; ++methodTable)
{
lua_pushstring(L, methodTable->name);
lua_pushlightuserdata(L, (void*)methodTable);
lua_pushcclosure(L, thunk, 1);
lua_settable(L, -3);
}
lua_remove(L, -1);
}
// Remember special case ElunaTemplate<Vehicle>::gcT
static int gcT(lua_State* L)
{
if (!manageMemory)
return 0;
// Get object pointer (and check type, no error)
T** ptrHold = static_cast<T**>(luaL_testudata(L, -1, tname));
if (ptrHold)
delete *ptrHold;
return 0;
}
static int push(lua_State* L, T const* obj)
{
if (!obj)
{
lua_pushnil(L);
return 1;
}
if (!manageMemory)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, sHookMgr->userdata_table);
lua_pushfstring(L, "%p", obj);
lua_gettable(L, -2);
if (!lua_isnoneornil(L, -1) && luaL_checkudata(L, -1, tname))
{
lua_remove(L, -2);
return 1;
}
lua_remove(L, -1);
// left userdata_table in stack
}
// Create new userdata
T const** ptrHold = (T const**)lua_newuserdata(L, sizeof(T const**));
if (!ptrHold)
{
ELUNA_LOG_ERROR("%s could not create new userdata", tname);
lua_remove(L, -1);
lua_pushnil(L);
return 1;
}
*ptrHold = obj;
// Set metatable for it
luaL_getmetatable(L, tname);
if (!lua_istable(L, -1))
{
ELUNA_LOG_ERROR("%s missing metatable", tname);
lua_pop(L, 2);
lua_pushnil(L);
return 1;
}
lua_setmetatable(L, -2);
if (!manageMemory)
{
lua_pushfstring(L, "%p", obj);
lua_pushvalue(L, -2);
lua_settable(L, -3);
lua_remove(L, -2);
}
return 1;
}
static T* check(lua_State* L, int narg, bool error = true)
{
T** ptrHold = static_cast<T**>(error ? luaL_checkudata(L, narg, tname) : lua_touserdata(L, narg));
if (!ptrHold)
{
if (error)
{
char buff[256];
snprintf(buff, 256, "%s expected, got %s", tname, luaL_typename(L, narg));
luaL_argerror(L, narg, buff);
}
return NULL;
}
if (!manageMemory)
{
// Check pointer validity
lua_rawgeti(L, LUA_REGISTRYINDEX, sHookMgr->userdata_table);
lua_pushfstring(L, "%p", *ptrHold);
lua_gettable(L, -2);
lua_remove(L, -2);
bool valid = lua_isuserdata(L, -1);
lua_remove(L, -1);
if (!valid)
{
char buff[256];
snprintf(buff, 256, "%s expected, got pointer to nonexisting object (%s). This should never happen", tname, luaL_typename(L, narg));
if (error)
{
luaL_argerror(L, narg, buff);
}
else
{
ELUNA_LOG_ERROR("%s", buff);
}
return NULL;
}
}
return *ptrHold;
}
static int thunk(lua_State* L)
{
T* obj = sEluna->CHECKOBJ<T>(L, 1); // get self
if (!obj)
return 0;
ElunaRegister<T>* l = static_cast<ElunaRegister<T>*>(lua_touserdata(L, lua_upvalueindex(1)));
int args = lua_gettop(L);
int expected = l->mfunc(L, obj);
args = lua_gettop(L) - args;
if (args < 0 || args > expected) // Assert instead?
{
ELUNA_LOG_ERROR("[Eluna]: %s returned unexpected amount of arguments %i out of %i. Report to devs", l->name, args, expected);
}
for (; args < expected; ++args)
lua_pushnil(L);
return expected;
}
static int tostringT(lua_State* L)
{
T* obj = sEluna->CHECKOBJ<T>(L, 1); // get self
if (obj)
{
lua_pushfstring(L, "%s: (%p)", tname, obj);
return 1;
}
lua_pushnil(L);
lua_replace(L, 1);
luaL_tolstring(L, 1, NULL);
return 1;
}
};
class LuaTaxiMgr
{
private:
static uint32 nodeId;
public:
static void StartTaxi(Player* player, uint32 pathid);
static uint32 AddPath(std::list<TaxiPathNodeEntry> nodes, uint32 mountA, uint32 mountH, uint32 price = 0, uint32 pathId = 0);
};
#endif