Allow scripting of instances w/ persistent data

Some fixes for TC and changes overall

Pass map object to hook via function arguments

The map object is no longer stored inside the instance data table.

Fix mistake in base64 decoder

It was failing whenever it encountered a '=' character, which is
completely valid.

Make ElunaInstanceAI::Load always load something

When it failed to load data, it was leaving nothing on the stack. Since
subsequent code expected Load to always load something, this was causing
issues.

Now, when Load fails to load anything, it just leaves a new empty table on
the stack.

Also: the error messages for Load have been improved.

Modify lua-marshal to allow saving of functions/userdata.

Some additional code was needed to save functions due to the inclusion of
a reference to _ENV within their upvalues (since Lua 5.2).

During encoding, a placeholder is left where the _ENV reference would be.
During decoding, a reference to the current _G is swapped with the
placeholder.

Make ElunaInstanceAI::Load re-initialize if data failed to load.

Also improve error messages by not including the raw data.

Improve storage format of upvalues

Instead of storing the upvalues by name, store by index. A wrapper is
still used in case the upvalue is nil, to prevent holes in the upvalues table.

A special field in the upvalues table, "E", is used to store the index of
the _ENV reference (if there was one). A reference to the current globals
table is set as the upvalue upon decoding.

Remove wrapping from upvalue storing, instead save amount of upvalues
This commit is contained in:
Patman64
2015-01-18 21:53:50 -05:00
committed by Rochet2
parent d57ca139b7
commit 31470c2cf7
14 changed files with 1564 additions and 25 deletions

View File

@@ -232,12 +232,13 @@ struct EventKey
* (CreatureEvents, GameObjectEvents, etc.). * (CreatureEvents, GameObjectEvents, etc.).
*/ */
template <typename T> template <typename T>
struct EntryKey : public EventKey<T> struct EntryKey
{ {
T event_id;
uint32 entry; uint32 entry;
EntryKey(T event_type, uint32 entry) : EntryKey(T event_id, uint32 entry) :
EventKey<T>(event_type), event_id(event_id),
entry(entry) entry(entry)
{} {}
}; };
@@ -247,13 +248,14 @@ struct EntryKey : public EventKey<T>
* (currently just CreatureEvents). * (currently just CreatureEvents).
*/ */
template <typename T> template <typename T>
struct UniqueObjectKey : public EventKey<T> struct UniqueObjectKey
{ {
T event_id;
uint64 guid; uint64 guid;
uint32 instance_id; uint32 instance_id;
UniqueObjectKey(T event_type, uint64 guid, uint32 instance_id) : UniqueObjectKey(T event_id, uint64 guid, uint32 instance_id) :
EventKey<T>(event_type), event_id(event_id),
guid(guid), guid(guid),
instance_id(instance_id) instance_id(instance_id)
{} {}

227
ElunaInstanceAI.cpp Normal file
View File

@@ -0,0 +1,227 @@
/*
* Copyright (C) 2010 - 2015 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
*/
#include "ElunaInstanceAI.h"
#include "ElunaUtility.h"
#include "lmarshal.h"
void ElunaInstanceAI::Initialize()
{
LOCK_ELUNA;
ASSERT(!sEluna->HasInstanceData(instance));
// Create a new table for instance data.
lua_State* L = sEluna->L;
lua_newtable(L);
sEluna->CreateInstanceData(instance);
sEluna->OnInitialize(this);
}
void ElunaInstanceAI::Load(const char* data)
{
LOCK_ELUNA;
// If we get passed NULL (i.e. `Reload` was called) then use
// the last known save data (or maybe just an empty string).
if (!data)
{
data = lastSaveData.c_str();
}
else // Otherwise, copy the new data into our buffer.
{
lastSaveData.assign(data);
}
if (data[0] == '\0')
{
ASSERT(!sEluna->HasInstanceData(instance));
// Create a new table for instance data.
lua_State* L = sEluna->L;
lua_newtable(L);
sEluna->CreateInstanceData(instance);
sEluna->OnLoad(this);
// Stack: (empty)
return;
}
size_t decodedLength;
const unsigned char* decodedData = ElunaUtil::DecodeData(data, &decodedLength);
lua_State* L = sEluna->L;
if (decodedData)
{
// Stack: (empty)
lua_pushcfunction(L, mar_decode);
lua_pushlstring(L, (const char*)decodedData, decodedLength);
// Stack: mar_decode, decoded_data
// Call `mar_decode` and check for success.
if (lua_pcall(L, 1, 1, 0) == 0)
{
// Stack: data
// Only use the data if it's a table.
if (lua_istable(L, -1))
{
sEluna->CreateInstanceData(instance);
// Stack: (empty)
sEluna->OnLoad(this);
// WARNING! lastSaveData might be different after `OnLoad` if the Lua code saved data.
}
else
{
ELUNA_LOG_ERROR("Error while loading instance data: Expected data to be a table (type 5), got type %d instead", lua_type(L, -1));
lua_pop(L, 1);
// Stack: (empty)
Initialize();
}
}
else
{
// Stack: error_message
ELUNA_LOG_ERROR("Error while parsing instance data with lua-marshal: %s", lua_tostring(L, -1));
lua_pop(L, 1);
// Stack: (empty)
Initialize();
}
delete[] decodedData;
}
else
{
ELUNA_LOG_ERROR("Error while decoding instance data: Data is not valid base-64");
Initialize();
}
}
const char* ElunaInstanceAI::Save() const
{
LOCK_ELUNA;
lua_State* L = sEluna->L;
// Stack: (empty)
/*
* Need to cheat because this method actually does modify this instance,
* even though it's declared as `const`.
*
* Declaring virtual methods as `const` is BAD!
* Don't dictate to children that their methods must be pure.
*/
ElunaInstanceAI* self = const_cast<ElunaInstanceAI*>(this);
lua_pushcfunction(L, mar_encode);
sEluna->PushInstanceData(L, self, false);
// Stack: mar_encode, instance_data
if (lua_pcall(L, 1, 1, 0) != 0)
{
// Stack: error_message
ELUNA_LOG_ERROR("Error while saving: %s", lua_tostring(L, -1));
lua_pop(L, 1);
return NULL;
}
// Stack: data
size_t dataLength;
const unsigned char* data = (const unsigned char*)lua_tolstring(L, -1, &dataLength);
ElunaUtil::EncodeData(data, dataLength, self->lastSaveData);
lua_pop(L, 1);
// Stack: (empty)
return lastSaveData.c_str();
}
uint32 ElunaInstanceAI::GetData(uint32 key) const
{
LOCK_ELUNA;
lua_State* L = sEluna->L;
// Stack: (empty)
sEluna->PushInstanceData(L, const_cast<ElunaInstanceAI*>(this), false);
// Stack: instance_data
Eluna::Push(L, key);
// Stack: instance_data, key
lua_gettable(L, -2);
// Stack: instance_data, value
uint32 value = Eluna::CHECKVAL<uint32>(L, -1, 0);
lua_pop(L, 2);
// Stack: (empty)
return value;
}
void ElunaInstanceAI::SetData(uint32 key, uint32 value)
{
LOCK_ELUNA;
lua_State* L = sEluna->L;
// Stack: (empty)
sEluna->PushInstanceData(L, this, false);
// Stack: instance_data
Eluna::Push(L, key);
Eluna::Push(L, value);
// Stack: instance_data, key, value
lua_settable(L, -3);
// Stack: instance_data
lua_pop(L, 1);
// Stack: (empty)
}
uint64 ElunaInstanceAI::GetData64(uint32 key) const
{
LOCK_ELUNA;
lua_State* L = sEluna->L;
// Stack: (empty)
sEluna->PushInstanceData(L, const_cast<ElunaInstanceAI*>(this), false);
// Stack: instance_data
Eluna::Push(L, key);
// Stack: instance_data, key
lua_gettable(L, -2);
// Stack: instance_data, value
uint64 value = Eluna::CHECKVAL<uint64>(L, -1, 0);
lua_pop(L, 2);
// Stack: (empty)
return value;
}
void ElunaInstanceAI::SetData64(uint32 key, uint64 value)
{
LOCK_ELUNA;
lua_State* L = sEluna->L;
// Stack: (empty)
sEluna->PushInstanceData(L, this, false);
// Stack: instance_data
Eluna::Push(L, key);
Eluna::Push(L, value);
// Stack: instance_data, key, value
lua_settable(L, -3);
// Stack: instance_data
lua_pop(L, 1);
// Stack: (empty)
}

151
ElunaInstanceAI.h Normal file
View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2010 - 2015 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_INSTANCE_DATA_H
#define _ELUNA_INSTANCE_DATA_H
#include "LuaEngine.h"
#ifdef TRINITY
#include "InstanceScript.h"
#else
#include "InstanceData.h"
#endif
/*
* This class is a small wrapper around `InstanceData`,
* allowing instances to be scripted with Eluna.
*
*
* Note 1
* ======
*
* Instances of `ElunaInstanceAI` are owned by the core, so they
* are not deleted when Eluna is reloaded. Thus `Load` is only called
* by the core once, no matter how many times Eluna is reloaded.
*
* However, when Eluna reloads, all instance data in Eluna is lost.
* So the solution is as follows:
*
* 1. Store the last save data in the member var `lastSaveData`.
*
* At first this is just the data given to us by the core when it calls `Load`,
* but later on once we start saving new data this is from Eluna.
*
* 2. When retrieving instance data from Eluna, check if it's missing.
*
* The data will be missing if Eluna is reloaded, since a new Lua state is created.
*
* 3. If it *is* missing, call `Reload`.
*
* This reloads the last known instance save data into Eluna, and calls the appropriate hooks.
*
*
* Note 2
* ======
*
* CMaNGOS expects some of these methods to be `const`. However, any of these
* methods are free to call `Save`, resulting in mutation of `lastSaveData`.
*
* Therefore, none of the hooks are `const`-safe, and `const_cast` is used
* to escape from these restrictions.
*/
class ElunaInstanceAI : public InstanceData
{
private:
// The last save data to pass through this class,
// either through `Load` or `Save`.
std::string lastSaveData;
// Used to prevent saving this AI while it's still loading,
// effectively nuking the data that's supposed to be loaded.
// Set to `false` while loading, `true` otherwise.
bool canSave;
public:
ElunaInstanceAI(Map* map) : InstanceData(map), canSave(true)
{
}
void Initialize() override;
/*
* These are responsible for serializing/deserializing the instance's
* data table to/from the core.
*/
void Load(const char* data) override;
#ifdef TRINITY
// Simply calls Save, since the functions are a bit different in name and data types on different cores
std::string GetSaveData() override
{
return Save();
}
const char* Save() const;
#else
const char* Save() const override;
#endif
/*
* Calls `Load` with the last save data that was passed to
* or from Eluna.
*
* See: big documentation blurb at the top of this class.
*/
void Reload()
{
Load(NULL);
}
/*
* These methods allow non-Lua scripts (e.g. DB, C++) to get/set instance data.
*/
uint32 GetData(uint32 key) const override;
void SetData(uint32 key, uint32 value) override;
uint64 GetData64(uint32 key) const override;
void SetData64(uint32 key, uint64 value) override;
/*
* These methods are just thin wrappers around Eluna.
*/
void Update(uint32 diff) override
{
// If Eluna is reloaded, it will be missing our instance data.
// Reload here instead of waiting for the next hook call (possibly never).
// This avoids having to have an empty Update hook handler just to trigger the reload.
if (!sEluna->HasInstanceData(instance))
Reload();
sEluna->OnUpdateInstance(this, diff);
}
bool IsEncounterInProgress() const override
{
return sEluna->OnCheckEncounterInProgress(const_cast<ElunaInstanceAI*>(this));
}
void OnPlayerEnter(Player* player) override
{
sEluna->OnPlayerEnterInstance(this, player);
}
#ifdef TRINITY
void OnGameObjectCreate(GameObject* gameobject) override
#else
void OnObjectCreate(GameObject* gameobject) override
#endif
{
sEluna->OnGameObjectCreate(this, gameobject);
}
void OnCreatureCreate(Creature* creature) override
{
sEluna->OnCreatureCreate(this, creature);
}
};
#endif // _ELUNA_INSTANCE_DATA_H

View File

@@ -85,3 +85,97 @@ bool ElunaUtil::WorldObjectInRangeCheck::operator()(WorldObject* u)
i_range = i_obj->GetDistance(u); i_range = i_obj->GetDistance(u);
return true; return true;
} }
static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'};
static char decoding_table[256];
static int mod_table[] = {0, 2, 1};
static void build_decoding_table()
{
for (int i = 0; i < 64; i++)
decoding_table[(unsigned char)encoding_table[i]] = i;
}
void ElunaUtil::EncodeData(const unsigned char* data, size_t input_length, std::string& output)
{
size_t output_length = 4 * ((input_length + 2) / 3);
char* buffer = new char[output_length];
for (size_t i = 0, j = 0; i < input_length;)
{
uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
buffer[j++] = encoding_table[(triple >> (3 * 6)) & 0x3F];
buffer[j++] = encoding_table[(triple >> (2 * 6)) & 0x3F];
buffer[j++] = encoding_table[(triple >> (1 * 6)) & 0x3F];
buffer[j++] = encoding_table[(triple >> (0 * 6)) & 0x3F];
}
for (int i = 0; i < mod_table[input_length % 3]; i++)
buffer[output_length - 1 - i] = '=';
output.assign(buffer, output_length); // Need length because `buffer` is not terminated!
delete[] buffer;
}
unsigned char* ElunaUtil::DecodeData(const char *data, size_t *output_length)
{
if (decoding_table['B'] == 0)
build_decoding_table();
size_t input_length = strlen(data);
if (input_length % 4 != 0)
return NULL;
// Make sure there's no invalid characters in the data.
for (size_t i = 0; i < input_length; ++i)
{
unsigned char byte = data[i];
if (byte == '=')
continue;
// Every invalid character (and 'A') will map to 0 (due to `calloc`).
if (decoding_table[byte] == 0 && byte != 'A')
return NULL;
}
*output_length = input_length / 4 * 3;
if (data[input_length - 1] == '=') (*output_length)--;
if (data[input_length - 2] == '=') (*output_length)--;
unsigned char *decoded_data = new unsigned char[*output_length];
if (!decoded_data)
return NULL;
for (size_t i = 0, j = 0; i < input_length;)
{
uint32_t sextet_a = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
uint32_t sextet_b = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
uint32_t sextet_c = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
uint32_t sextet_d = data[i] == '=' ? 0 & i++ : decoding_table[data[i++]];
uint32_t triple = (sextet_a << (3 * 6))
+ (sextet_b << (2 * 6))
+ (sextet_c << (1 * 6))
+ (sextet_d << (0 * 6));
if (j < *output_length) decoded_data[j++] = (triple >> (2 * 8)) & 0xFF;
if (j < *output_length) decoded_data[j++] = (triple >> (1 * 8)) & 0xFF;
if (j < *output_length) decoded_data[j++] = (triple >> (0 * 8)) & 0xFF;
}
return decoded_data;
}

View File

@@ -137,6 +137,18 @@ namespace ElunaUtil
private: private:
LockType _lock; LockType _lock;
}; };
/*
* Encodes `data` in Base-64 and store the result in `output`.
*/
void EncodeData(const unsigned char* data, size_t input_length, std::string& output);
/*
* Decodes `data` from Base-64 and returns a pointer to the result, or `NULL` on error.
*
* The returned result buffer must be `delete[]`ed by the caller.
*/
unsigned char* DecodeData(const char* data, size_t *output_length);
}; };
#endif #endif

View File

@@ -499,7 +499,7 @@ namespace LuaGlobalFunctions
static int RegisterEntryHelper(Eluna* E, lua_State* L, int regtype) static int RegisterEntryHelper(Eluna* E, lua_State* L, int regtype)
{ {
uint32 entry = Eluna::CHECKVAL<uint32>(L, 1); uint32 id = Eluna::CHECKVAL<uint32>(L, 1);
uint32 ev = Eluna::CHECKVAL<uint32>(L, 2); uint32 ev = Eluna::CHECKVAL<uint32>(L, 2);
luaL_checktype(L, 3, LUA_TFUNCTION); luaL_checktype(L, 3, LUA_TFUNCTION);
uint32 shots = Eluna::CHECKVAL<uint32>(L, 4, 0); uint32 shots = Eluna::CHECKVAL<uint32>(L, 4, 0);
@@ -507,7 +507,7 @@ namespace LuaGlobalFunctions
lua_pushvalue(L, 3); lua_pushvalue(L, 3);
int functionRef = luaL_ref(L, LUA_REGISTRYINDEX); int functionRef = luaL_ref(L, LUA_REGISTRYINDEX);
if (functionRef >= 0) if (functionRef >= 0)
return E->Register(L, regtype, entry, 0, 0, ev, functionRef, shots); return E->Register(L, regtype, id, 0, 0, ev, functionRef, shots);
else else
luaL_argerror(L, 3, "unable to make a ref to function"); luaL_argerror(L, 3, "unable to make a ref to function");
return 0; return 0;
@@ -923,6 +923,60 @@ namespace LuaGlobalFunctions
return RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM_GOSSIP); return RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM_GOSSIP);
} }
/**
* Registers a [Map] event handler for all instance of a [Map].
*
* <pre>
* enum InstanceEvents
* {
* INSTANCE_EVENT_ON_INITIALIZE = 1, // (event, instance_data, map)
* INSTANCE_EVENT_ON_LOAD = 2, // (event, instance_data, map)
* INSTANCE_EVENT_ON_UPDATE = 3, // (event, instance_data, map, diff)
* INSTANCE_EVENT_ON_PLAYER_ENTER = 4, // (event, instance_data, map, player)
* INSTANCE_EVENT_ON_CREATURE_CREATE = 5, // (event, instance_data, map, creature)
* INSTANCE_EVENT_ON_GAMEOBJECT_CREATE = 6, // (event, instance_data, map, go)
* INSTANCE_EVENT_ON_CHECK_ENCOUNTER_IN_PROGRESS = 7, // (event, instance_data, map)
* INSTANCE_EVENT_COUNT
* };
* </pre>
*
* @param uint32 map_id : ID of a [Map]
* @param uint32 event : [Map] event ID, refer to MapEvents above
* @param function function : function to register
* @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function"
*/
int RegisterMapEvent(Eluna* E, lua_State* L)
{
return RegisterEntryHelper(E, L, Hooks::REGTYPE_MAP);
}
/**
* Registers a [Map] event handler for one instance of a [Map].
*
* <pre>
* enum InstanceEvents
* {
* INSTANCE_EVENT_ON_INITIALIZE = 1, // (event, instance_data, map)
* INSTANCE_EVENT_ON_LOAD = 2, // (event, instance_data, map)
* INSTANCE_EVENT_ON_UPDATE = 3, // (event, instance_data, map, diff)
* INSTANCE_EVENT_ON_PLAYER_ENTER = 4, // (event, instance_data, map, player)
* INSTANCE_EVENT_ON_CREATURE_CREATE = 5, // (event, instance_data, map, creature)
* INSTANCE_EVENT_ON_GAMEOBJECT_CREATE = 6, // (event, instance_data, map, go)
* INSTANCE_EVENT_ON_CHECK_ENCOUNTER_IN_PROGRESS = 7, // (event, instance_data, map)
* INSTANCE_EVENT_COUNT
* };
* </pre>
*
* @param uint32 instance_id : ID of an instance of a [Map]
* @param uint32 event : [Map] event ID, refer to MapEvents above
* @param function function : function to register
* @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function"
*/
int RegisterInstanceEvent(Eluna* E, lua_State* L)
{
return RegisterEntryHelper(E, L, Hooks::REGTYPE_INSTANCE);
}
/** /**
* Registers a [Player] gossip event handler. * Registers a [Player] gossip event handler.
* *
@@ -2898,5 +2952,71 @@ namespace LuaGlobalFunctions
} }
return 0; return 0;
} }
/**
* Unbinds event handlers for either all of a non-instanced [Map]'s events, or one type of event.
*
* If `event_type` is `nil`, all the non-instanced [Map]'s event handlers are cleared.
*
* Otherwise, only event handlers for `event_type` are cleared.
*
* @proto (map_id)
* @proto (map_id, event_type)
* @param uint32 map_id : the ID of a [Map]
* @param uint32 event_type : the event whose handlers will be cleared, see [Global:RegisterPlayerGossipEvent]
*/
int ClearMapEvents(Eluna* E, lua_State* L)
{
typedef EntryKey<Hooks::InstanceEvents> Key;
if (lua_isnoneornil(L, 2))
{
uint32 entry = Eluna::CHECKVAL<uint32>(L, 1);
for (uint32 i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i)
E->MapEventBindings->Clear(Key((Hooks::InstanceEvents)i, entry));
}
else
{
uint32 entry = Eluna::CHECKVAL<uint32>(L, 1);
uint32 event_type = Eluna::CHECKVAL<uint32>(L, 2);
E->MapEventBindings->Clear(Key((Hooks::InstanceEvents)event_type, entry));
}
return 0;
}
/**
* Unbinds event handlers for either all of an instanced [Map]'s events, or one type of event.
*
* If `event_type` is `nil`, all the instanced [Map]'s event handlers are cleared.
*
* Otherwise, only event handlers for `event_type` are cleared.
*
* @proto (instance_id)
* @proto (instance_id, event_type)
* @param uint32 entry : the ID of an instance of a [Map]
* @param uint32 event_type : the event whose handlers will be cleared, see [Global:RegisterPlayerGossipEvent]
*/
int ClearInstanceEvents(Eluna* E, lua_State* L)
{
typedef EntryKey<Hooks::InstanceEvents> Key;
if (lua_isnoneornil(L, 2))
{
uint32 entry = Eluna::CHECKVAL<uint32>(L, 1);
for (uint32 i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i)
E->InstanceEventBindings->Clear(Key((Hooks::InstanceEvents)i, entry));
}
else
{
uint32 entry = Eluna::CHECKVAL<uint32>(L, 1);
uint32 event_type = Eluna::CHECKVAL<uint32>(L, 2);
E->InstanceEventBindings->Clear(Key((Hooks::InstanceEvents)event_type, entry));
}
return 0;
}
} }
#endif #endif

14
Hooks.h
View File

@@ -84,6 +84,8 @@ namespace Hooks
REGTYPE_ITEM_GOSSIP, REGTYPE_ITEM_GOSSIP,
REGTYPE_PLAYER_GOSSIP, REGTYPE_PLAYER_GOSSIP,
REGTYPE_BG, REGTYPE_BG,
REGTYPE_MAP,
REGTYPE_INSTANCE,
REGTYPE_COUNT REGTYPE_COUNT
}; };
@@ -332,6 +334,18 @@ namespace Hooks
BG_EVENT_ON_PRE_DESTROY = 4, // (event, bg, bgId, instanceId) - Needs to be added to TC BG_EVENT_ON_PRE_DESTROY = 4, // (event, bg, bgId, instanceId) - Needs to be added to TC
BG_EVENT_COUNT BG_EVENT_COUNT
}; };
enum InstanceEvents
{
INSTANCE_EVENT_ON_INITIALIZE = 1, // (event, instance_data, map)
INSTANCE_EVENT_ON_LOAD = 2, // (event, instance_data, map)
INSTANCE_EVENT_ON_UPDATE = 3, // (event, instance_data, map, diff)
INSTANCE_EVENT_ON_PLAYER_ENTER = 4, // (event, instance_data, map, player)
INSTANCE_EVENT_ON_CREATURE_CREATE = 5, // (event, instance_data, map, creature)
INSTANCE_EVENT_ON_GAMEOBJECT_CREATE = 6, // (event, instance_data, map, go)
INSTANCE_EVENT_ON_CHECK_ENCOUNTER_IN_PROGRESS = 7, // (event, instance_data, map)
INSTANCE_EVENT_COUNT
};
}; };
#endif // _HOOKS_H #endif // _HOOKS_H

83
InstanceHooks.cpp Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2010 - 2015 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
*/
#include "Hooks.h"
#include "HookHelpers.h"
#include "LuaEngine.h"
#include "BindingMap.h"
#include "ElunaIncludes.h"
#include "ElunaTemplate.h"
#include "ElunaInstanceAI.h"
using namespace Hooks;
#define START_HOOK(EVENT, AI) \
if (!IsEnabled())\
return;\
auto mapKey = EntryKey<InstanceEvents>(EVENT, AI->instance->GetId());\
auto instanceKey = EntryKey<InstanceEvents>(EVENT, AI->instance->GetInstanceId());\
if (!MapEventBindings->HasBindingsFor(mapKey) && !InstanceEventBindings->HasBindingsFor(instanceKey))\
return;\
LOCK_ELUNA;\
PushInstanceData(L, AI);\
Push(AI->instance)
#define START_HOOK_WITH_RETVAL(EVENT, AI, RETVAL) \
if (!IsEnabled())\
return RETVAL;\
auto mapKey = EntryKey<InstanceEvents>(EVENT, AI->instance->GetId());\
auto instanceKey = EntryKey<InstanceEvents>(EVENT, AI->instance->GetInstanceId());\
if (!MapEventBindings->HasBindingsFor(mapKey) && !InstanceEventBindings->HasBindingsFor(instanceKey))\
return RETVAL;\
LOCK_ELUNA;\
PushInstanceData(L, AI);\
Push(AI->instance)
void Eluna::OnInitialize(ElunaInstanceAI* ai)
{
START_HOOK(INSTANCE_EVENT_ON_INITIALIZE, ai);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
void Eluna::OnLoad(ElunaInstanceAI* ai)
{
START_HOOK(INSTANCE_EVENT_ON_LOAD, ai);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
void Eluna::OnUpdateInstance(ElunaInstanceAI* ai, uint32 diff)
{
START_HOOK(INSTANCE_EVENT_ON_UPDATE, ai);
Push(diff);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
void Eluna::OnPlayerEnterInstance(ElunaInstanceAI* ai, Player* player)
{
START_HOOK(INSTANCE_EVENT_ON_PLAYER_ENTER, ai);
Push(player);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
void Eluna::OnCreatureCreate(ElunaInstanceAI* ai, Creature* creature)
{
START_HOOK(INSTANCE_EVENT_ON_CREATURE_CREATE, ai);
Push(creature);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
void Eluna::OnGameObjectCreate(ElunaInstanceAI* ai, GameObject* gameobject)
{
START_HOOK(INSTANCE_EVENT_ON_GAMEOBJECT_CREATE, ai);
Push(gameobject);
CallAllFunctions(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}
bool Eluna::OnCheckEncounterInProgress(ElunaInstanceAI* ai)
{
START_HOOK_WITH_RETVAL(INSTANCE_EVENT_ON_CHECK_ENCOUNTER_IN_PROGRESS, ai, false);
return CallAllFunctionsBool(MapEventBindings, InstanceEventBindings, mapKey, instanceKey);
}

View File

@@ -12,6 +12,7 @@
#include "ElunaTemplate.h" #include "ElunaTemplate.h"
#include "ElunaUtility.h" #include "ElunaUtility.h"
#include "ElunaCreatureAI.h" #include "ElunaCreatureAI.h"
#include "ElunaInstanceAI.h"
#ifdef USING_BOOST #ifdef USING_BOOST
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
@@ -47,6 +48,12 @@ void Eluna::Initialize()
LOCK_ELUNA; LOCK_ELUNA;
ASSERT(!IsInitialized()); ASSERT(!IsInitialized());
#ifdef TRINITY
// For instance data the data column needs to be able to hold more than 255 characters (tinytext)
// so we change it to TEXT automatically on startup
CharacterDatabase.DirectExecute("ALTER TABLE `instance` CHANGE COLUMN `data` `data` TEXT NOT NULL");
#endif
LoadScriptPaths(); LoadScriptPaths();
// Must be before creating GEluna // Must be before creating GEluna
@@ -152,6 +159,8 @@ GameObjectGossipBindings(NULL),
ItemEventBindings(NULL), ItemEventBindings(NULL),
ItemGossipBindings(NULL), ItemGossipBindings(NULL),
PlayerGossipBindings(NULL), PlayerGossipBindings(NULL),
MapEventBindings(NULL),
InstanceEventBindings(NULL),
CreatureUniqueBindings(NULL) CreatureUniqueBindings(NULL)
{ {
@@ -185,6 +194,9 @@ void Eluna::CloseLua()
if (L) if (L)
lua_close(L); lua_close(L);
L = NULL; L = NULL;
instanceDataRefs.clear();
continentDataRefs.clear();
} }
void Eluna::OpenLua() void Eluna::OpenLua()
@@ -243,6 +255,8 @@ void Eluna::CreateBindStores()
ItemEventBindings = new BindingMap< EntryKey<Hooks::ItemEvents> >(L); ItemEventBindings = new BindingMap< EntryKey<Hooks::ItemEvents> >(L);
ItemGossipBindings = new BindingMap< EntryKey<Hooks::GossipEvents> >(L); ItemGossipBindings = new BindingMap< EntryKey<Hooks::GossipEvents> >(L);
PlayerGossipBindings = new BindingMap< EntryKey<Hooks::GossipEvents> >(L); PlayerGossipBindings = new BindingMap< EntryKey<Hooks::GossipEvents> >(L);
MapEventBindings = new BindingMap< EntryKey<Hooks::InstanceEvents> >(L);
InstanceEventBindings = new BindingMap< EntryKey<Hooks::InstanceEvents> >(L);
CreatureUniqueBindings = new BindingMap< UniqueObjectKey<Hooks::CreatureEvents> >(L); CreatureUniqueBindings = new BindingMap< UniqueObjectKey<Hooks::CreatureEvents> >(L);
} }
@@ -264,6 +278,8 @@ void Eluna::DestroyBindStores()
delete ItemGossipBindings; delete ItemGossipBindings;
delete PlayerGossipBindings; delete PlayerGossipBindings;
delete BGEventBindings; delete BGEventBindings;
delete MapEventBindings;
delete InstanceEventBindings;
delete CreatureUniqueBindings; delete CreatureUniqueBindings;
@@ -282,6 +298,8 @@ void Eluna::DestroyBindStores()
ItemGossipBindings = NULL; ItemGossipBindings = NULL;
PlayerGossipBindings = NULL; PlayerGossipBindings = NULL;
BGEventBindings = NULL; BGEventBindings = NULL;
MapEventBindings = NULL;
InstanceEventBindings = NULL;
CreatureUniqueBindings = NULL; CreatureUniqueBindings = NULL;
} }
@@ -1136,6 +1154,24 @@ int Eluna::Register(lua_State* L, uint8 regtype, uint32 entry, uint64 guid, uint
return 1; // Stack: callback return 1; // Stack: callback
} }
break; break;
case Hooks::REGTYPE_MAP:
if (event_id < Hooks::INSTANCE_EVENT_COUNT)
{
auto key = EntryKey<Hooks::InstanceEvents>((Hooks::InstanceEvents)event_id, entry);
bindingID = MapEventBindings->Insert(key, functionRef, shots);
createCancelCallback(L, bindingID, MapEventBindings);
return 1; // Stack: callback
}
break;
case Hooks::REGTYPE_INSTANCE:
if (event_id < Hooks::INSTANCE_EVENT_COUNT)
{
auto key = EntryKey<Hooks::InstanceEvents>((Hooks::InstanceEvents)event_id, entry);
bindingID = InstanceEventBindings->Insert(key, functionRef, shots);
createCancelCallback(L, bindingID, InstanceEventBindings);
return 1; // Stack: callback
}
break;
} }
luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_unref(L, LUA_REGISTRYINDEX, functionRef);
luaL_error(L, "Unknown event type (regtype %hhu, event %u, entry %u, guid " UI64FMTD ", instance %u)", regtype, event_id, entry, guid, instanceId); luaL_error(L, "Unknown event type (regtype %hhu, event %u, entry %u, guid " UI64FMTD ", instance %u)", regtype, event_id, entry, guid, instanceId);
@@ -1205,3 +1241,107 @@ CreatureAI* Eluna::GetAI(Creature* creature)
return NULL; return NULL;
} }
InstanceData* Eluna::GetInstanceData(Map* map)
{
if (!IsEnabled())
return NULL;
for (int i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i)
{
Hooks::InstanceEvents event_id = (Hooks::InstanceEvents)i;
auto key = EntryKey<Hooks::InstanceEvents>(event_id, map->GetId());
if (MapEventBindings->HasBindingsFor(key) ||
InstanceEventBindings->HasBindingsFor(key))
return new ElunaInstanceAI(map);
}
return NULL;
}
bool Eluna::HasInstanceData(Map const* map)
{
if (!map->Instanceable())
return continentDataRefs.find(map->GetId()) != continentDataRefs.end();
else
return instanceDataRefs.find(map->GetInstanceId()) != instanceDataRefs.end();
}
void Eluna::CreateInstanceData(Map const* map)
{
ASSERT(lua_istable(L, -1));
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
if (!map->Instanceable())
{
uint32 mapId = map->GetId();
// If there's another table that was already stored for the map, unref it.
auto mapRef = continentDataRefs.find(mapId);
if (mapRef != continentDataRefs.end())
{
luaL_unref(L, LUA_REGISTRYINDEX, mapRef->second);
}
continentDataRefs[mapId] = ref;
}
else
{
uint32 instanceId = map->GetInstanceId();
// If there's another table that was already stored for the instance, unref it.
auto instRef = instanceDataRefs.find(instanceId);
if (instRef != instanceDataRefs.end())
{
luaL_unref(L, LUA_REGISTRYINDEX, instRef->second);
}
instanceDataRefs[instanceId] = ref;
}
}
/*
* Unrefs the instanceId related events and data
* Does all required actions for when an instance is freed.
*/
void Eluna::FreeInstanceId(uint32 instanceId)
{
LOCK_ELUNA;
for (int i = 1; i < Hooks::INSTANCE_EVENT_COUNT; ++i)
{
auto key = EntryKey<Hooks::InstanceEvents>((Hooks::InstanceEvents)i, instanceId);
if (MapEventBindings->HasBindingsFor(key))
MapEventBindings->Clear(key);
if (InstanceEventBindings->HasBindingsFor(key))
InstanceEventBindings->Clear(key);
if (instanceDataRefs.find(instanceId) != instanceDataRefs.end())
{
luaL_unref(L, LUA_REGISTRYINDEX, instanceDataRefs[instanceId]);
instanceDataRefs.erase(instanceId);
}
}
}
void Eluna::PushInstanceData(lua_State* L, ElunaInstanceAI* ai, bool incrementCounter)
{
// Check if the instance data is missing (i.e. someone reloaded Eluna).
if (!HasInstanceData(ai->instance))
ai->Reload();
// Get the instance data table from the registry.
if (!ai->instance->Instanceable())
lua_rawgeti(L, LUA_REGISTRYINDEX, continentDataRefs[ai->instance->GetId()]);
else
lua_rawgeti(L, LUA_REGISTRYINDEX, instanceDataRefs[ai->instance->GetInstanceId()]);
ASSERT(lua_istable(L, -1));
if (incrementCounter)
++push_counter;
}

View File

@@ -19,6 +19,7 @@
#include "Weather.h" #include "Weather.h"
#include "World.h" #include "World.h"
#include "Hooks.h" #include "Hooks.h"
#include "ElunaUtility.h"
extern "C" extern "C"
{ {
@@ -56,6 +57,13 @@ class GameObjectAI;
#endif #endif
class Guild; class Guild;
class Group; class Group;
#ifdef TRINITY
class InstanceScript;
typedef InstanceScript InstanceData;
#else
class InstanceData;
#endif
class ElunaInstanceAI;
class Item; class Item;
class Pet; class Pet;
class Player; class Player;
@@ -136,6 +144,11 @@ private:
uint8 push_counter; uint8 push_counter;
bool enabled; bool enabled;
// Map from instance ID -> Lua table ref
std::unordered_map<uint32, int> instanceDataRefs;
// Map from map ID -> Lua table ref
std::unordered_map<uint32, int> continentDataRefs;
Eluna(); Eluna();
~Eluna(); ~Eluna();
@@ -184,6 +197,23 @@ private:
return CallAllFunctionsBool<K, K>(bindings, NULL, key, key, default_value); return CallAllFunctionsBool<K, K>(bindings, NULL, key, key, default_value);
} }
// Non-static pushes, to be used in hooks.
// These just call the correct static version with the main thread's Lua state.
void Push() { Push(L); ++push_counter; }
void Push(const long long value) { Push(L, value); ++push_counter; }
void Push(const unsigned long long value) { Push(L, value); ++push_counter; }
void Push(const long value) { Push(L, value); ++push_counter; }
void Push(const unsigned long value) { Push(L, value); ++push_counter; }
void Push(const int value) { Push(L, value); ++push_counter; }
void Push(const unsigned int value) { Push(L, value); ++push_counter; }
void Push(const bool value) { Push(L, value); ++push_counter; }
void Push(const float value) { Push(L, value); ++push_counter; }
void Push(const double value) { Push(L, value); ++push_counter; }
void Push(const std::string& value) { Push(L, value); ++push_counter; }
void Push(const char* value) { Push(L, value); ++push_counter; }
template<typename T>
void Push(T const* ptr) { Push(L, ptr); ++push_counter; }
public: public:
static Eluna* GEluna; static Eluna* GEluna;
@@ -205,6 +235,8 @@ public:
BindingMap< EntryKey<Hooks::ItemEvents> >* ItemEventBindings; BindingMap< EntryKey<Hooks::ItemEvents> >* ItemEventBindings;
BindingMap< EntryKey<Hooks::GossipEvents> >* ItemGossipBindings; BindingMap< EntryKey<Hooks::GossipEvents> >* ItemGossipBindings;
BindingMap< EntryKey<Hooks::GossipEvents> >* PlayerGossipBindings; BindingMap< EntryKey<Hooks::GossipEvents> >* PlayerGossipBindings;
BindingMap< EntryKey<Hooks::InstanceEvents> >* MapEventBindings;
BindingMap< EntryKey<Hooks::InstanceEvents> >* InstanceEventBindings;
BindingMap< UniqueObjectKey<Hooks::CreatureEvents> >* CreatureUniqueBindings; BindingMap< UniqueObjectKey<Hooks::CreatureEvents> >* CreatureUniqueBindings;
@@ -239,29 +271,36 @@ public:
ElunaTemplate<T>::Push(luastate, ptr); ElunaTemplate<T>::Push(luastate, ptr);
} }
/*
* Returns `true` if Eluna has instance data for `map`.
*/
bool HasInstanceData(Map const* map);
/*
* Use the top element of the stack as the instance data table for `map`,
* then pops it off the stack.
*/
void CreateInstanceData(Map const* map);
/*
* Retrieve the instance data for the `Map` scripted by `ai` and push it
* onto the stack.
*
* An `ElunaInstanceAI` is needed because the instance data might
* not exist (i.e. Eluna has been reloaded).
*
* In that case, the AI is "reloaded" (new instance data table is created
* and loaded with the last known save state, and `Load`/`Initialize`
* hooks are called).
*/
void PushInstanceData(lua_State* L, ElunaInstanceAI* ai, bool incrementCounter = true);
void RunScripts(); void RunScripts();
bool ShouldReload() const { return reload; } bool ShouldReload() const { return reload; }
bool IsEnabled() const { return enabled && IsInitialized(); } bool IsEnabled() const { return enabled && IsInitialized(); }
bool HasLuaState() const { return L != NULL; } bool HasLuaState() const { return L != NULL; }
int Register(lua_State* L, uint8 reg, uint32 entry, uint64 guid, uint32 instanceId, uint32 event_id, int functionRef, uint32 shots); int Register(lua_State* L, uint8 reg, uint32 entry, uint64 guid, uint32 instanceId, uint32 event_id, int functionRef, uint32 shots);
// Non-static pushes, to be used in hooks.
// These just call the correct static version with the main thread's Lua state.
void Push() { Push(L); ++push_counter; }
void Push(const long long value) { Push(L, value); ++push_counter; }
void Push(const unsigned long long value) { Push(L, value); ++push_counter; }
void Push(const long value) { Push(L, value); ++push_counter; }
void Push(const unsigned long value) { Push(L, value); ++push_counter; }
void Push(const int value) { Push(L, value); ++push_counter; }
void Push(const unsigned int value) { Push(L, value); ++push_counter; }
void Push(const bool value) { Push(L, value); ++push_counter; }
void Push(const float value) { Push(L, value); ++push_counter; }
void Push(const double value) { Push(L, value); ++push_counter; }
void Push(const std::string& value) { Push(L, value); ++push_counter; }
void Push(const char* value) { Push(L, value); ++push_counter; }
template<typename T>
void Push(T const* ptr) { Push(L, ptr); ++push_counter; }
// Checks // Checks
template<typename T> static T CHECKVAL(lua_State* luastate, int narg); template<typename T> static T CHECKVAL(lua_State* luastate, int narg);
template<typename T> static T CHECKVAL(lua_State* luastate, int narg, T def) template<typename T> static T CHECKVAL(lua_State* luastate, int narg, T def)
@@ -275,6 +314,8 @@ public:
static ElunaObject* CHECKTYPE(lua_State* luastate, int narg, const char *tname, bool error = true); static ElunaObject* CHECKTYPE(lua_State* luastate, int narg, const char *tname, bool error = true);
CreatureAI* GetAI(Creature* creature); CreatureAI* GetAI(Creature* creature);
InstanceData* GetInstanceData(Map* map);
void FreeInstanceId(uint32 instanceId);
/* Custom */ /* Custom */
void OnTimedEvent(int funcRef, uint32 delay, uint32 calls, WorldObject* obj); void OnTimedEvent(int funcRef, uint32 delay, uint32 calls, WorldObject* obj);
@@ -454,6 +495,15 @@ public:
void OnRemove(Creature* creature); void OnRemove(Creature* creature);
void OnRemove(GameObject* gameobject); void OnRemove(GameObject* gameobject);
/* Instance */
void OnInitialize(ElunaInstanceAI* ai);
void OnLoad(ElunaInstanceAI* ai);
void OnUpdateInstance(ElunaInstanceAI* ai, uint32 diff);
void OnPlayerEnterInstance(ElunaInstanceAI* ai, Player* player);
void OnCreatureCreate(ElunaInstanceAI* ai, Creature* creature);
void OnGameObjectCreate(ElunaInstanceAI* ai, GameObject* gameobject);
bool OnCheckEncounterInProgress(ElunaInstanceAI* ai);
/* World */ /* World */
void OnOpenStateChange(bool open); void OnOpenStateChange(bool open);
void OnConfigLoad(bool reload); void OnConfigLoad(bool reload);

View File

@@ -54,6 +54,8 @@ ElunaGlobal::ElunaRegister GlobalMethods[] =
{ "RegisterItemGossipEvent", &LuaGlobalFunctions::RegisterItemGossipEvent }, // RegisterItemGossipEvent(entry, event, function) { "RegisterItemGossipEvent", &LuaGlobalFunctions::RegisterItemGossipEvent }, // RegisterItemGossipEvent(entry, event, function)
{ "RegisterPlayerGossipEvent", &LuaGlobalFunctions::RegisterPlayerGossipEvent }, // RegisterPlayerGossipEvent(menu_id, event, function) { "RegisterPlayerGossipEvent", &LuaGlobalFunctions::RegisterPlayerGossipEvent }, // RegisterPlayerGossipEvent(menu_id, event, function)
{ "RegisterBGEvent", &LuaGlobalFunctions::RegisterBGEvent }, // RegisterBGEvent(event, function) { "RegisterBGEvent", &LuaGlobalFunctions::RegisterBGEvent }, // RegisterBGEvent(event, function)
{ "RegisterMapEvent", &LuaGlobalFunctions::RegisterMapEvent },
{ "RegisterInstanceEvent", &LuaGlobalFunctions::RegisterInstanceEvent },
{ "ClearBattleGroundEvents", &LuaGlobalFunctions::ClearBattleGroundEvents }, { "ClearBattleGroundEvents", &LuaGlobalFunctions::ClearBattleGroundEvents },
{ "ClearCreatureEvents", &LuaGlobalFunctions::ClearCreatureEvents }, { "ClearCreatureEvents", &LuaGlobalFunctions::ClearCreatureEvents },
@@ -69,6 +71,8 @@ ElunaGlobal::ElunaRegister GlobalMethods[] =
{ "ClearPlayerEvents", &LuaGlobalFunctions::ClearPlayerEvents }, { "ClearPlayerEvents", &LuaGlobalFunctions::ClearPlayerEvents },
{ "ClearPlayerGossipEvents", &LuaGlobalFunctions::ClearPlayerGossipEvents }, { "ClearPlayerGossipEvents", &LuaGlobalFunctions::ClearPlayerGossipEvents },
{ "ClearServerEvents", &LuaGlobalFunctions::ClearServerEvents }, { "ClearServerEvents", &LuaGlobalFunctions::ClearServerEvents },
{ "ClearMapEvents", &LuaGlobalFunctions::ClearMapEvents },
{ "ClearInstanceEvents", &LuaGlobalFunctions::ClearInstanceEvents },
// Getters // Getters
{ "GetLuaEngine", &LuaGlobalFunctions::GetLuaEngine }, { "GetLuaEngine", &LuaGlobalFunctions::GetLuaEngine },
@@ -1190,6 +1194,7 @@ ElunaRegister<Map> MapMethods[] =
{ "GetName", &LuaMap::GetName }, // :GetName() - Returns the map's name UNDOCUMENTED { "GetName", &LuaMap::GetName }, // :GetName() - Returns the map's name UNDOCUMENTED
{ "GetDifficulty", &LuaMap::GetDifficulty }, // :GetDifficulty() - Returns the map's difficulty UNDOCUMENTED { "GetDifficulty", &LuaMap::GetDifficulty }, // :GetDifficulty() - Returns the map's difficulty UNDOCUMENTED
{ "GetInstanceId", &LuaMap::GetInstanceId }, // :GetInstanceId() - Returns the map's instance ID UNDOCUMENTED { "GetInstanceId", &LuaMap::GetInstanceId }, // :GetInstanceId() - Returns the map's instance ID UNDOCUMENTED
{ "GetInstanceData", &LuaMap::GetInstanceData },
{ "GetPlayerCount", &LuaMap::GetPlayerCount }, // :GetPlayerCount() - Returns the amount of players on map except GM's UNDOCUMENTED { "GetPlayerCount", &LuaMap::GetPlayerCount }, // :GetPlayerCount() - Returns the amount of players on map except GM's UNDOCUMENTED
{ "GetMapId", &LuaMap::GetMapId }, // :GetMapId() - Returns the map's ID UNDOCUMENTED { "GetMapId", &LuaMap::GetMapId }, // :GetMapId() - Returns the map's ID UNDOCUMENTED
{ "GetAreaId", &LuaMap::GetAreaId }, // :GetAreaId(x, y, z) - Returns the map's area ID based on coords UNDOCUMENTED { "GetAreaId", &LuaMap::GetAreaId }, // :GetAreaId(x, y, z) - Returns the map's area ID based on coords UNDOCUMENTED
@@ -1211,6 +1216,9 @@ ElunaRegister<Map> MapMethods[] =
#endif #endif
{ "IsRaid", &LuaMap::IsRaid }, // :IsRaid() - Returns the true if the map is a raid map, else false UNDOCUMENTED { "IsRaid", &LuaMap::IsRaid }, // :IsRaid() - Returns the true if the map is a raid map, else false UNDOCUMENTED
// Other
{ "SaveInstanceData", &LuaMap::SaveInstanceData },
{ NULL, NULL }, { NULL, NULL },
}; };

View File

@@ -7,6 +7,8 @@
#ifndef MAPMETHODS_H #ifndef MAPMETHODS_H
#define MAPMETHODS_H #define MAPMETHODS_H
#include "ElunaInstanceAI.h"
/*** /***
* A game map, e.g. Azeroth, Eastern Kingdoms, the Molten Core, etc. * A game map, e.g. Azeroth, Eastern Kingdoms, the Molten Core, etc.
* *
@@ -273,5 +275,50 @@ namespace LuaMap
#endif #endif
return 0; return 0;
} }
/**
* Gets the instance data table for the [Map], if it exists.
*
* The instance must be scripted using Eluna for this to succeed.
* If the instance is scripted in C++ this will return `nil`.
*
* @return table instance_data : instance data table, or `nil`
*/
int GetInstanceData(Eluna* E, lua_State* L, Map* map)
{
#ifdef TRINITY
ElunaInstanceAI* iAI = NULL;
if (InstanceMap* inst = map->ToInstanceMap())
iAI = dynamic_cast<ElunaInstanceAI*>(inst->GetInstanceScript());
#else
ElunaInstanceAI* iAI = dynamic_cast<ElunaInstanceAI*>(map->GetInstanceData());
#endif
if (iAI)
sEluna->PushInstanceData(L, iAI, false);
else
Eluna::Push(L); // nil
return 1;
}
/**
* Saves the [Map]'s instance data to the database.
*/
int SaveInstanceData(Eluna* E, lua_State* L, Map* map)
{
#ifdef TRINITY
ElunaInstanceAI* iAI = NULL;
if (InstanceMap* inst = map->ToInstanceMap())
iAI = dynamic_cast<ElunaInstanceAI*>(inst->GetInstanceScript());
#else
ElunaInstanceAI* iAI = dynamic_cast<ElunaInstanceAI*>(map->GetInstanceData());
#endif
if (iAI)
iAI->SaveToDB();
return 0;
}
}; };
#endif #endif

579
lmarshal.cpp Normal file
View File

@@ -0,0 +1,579 @@
/*
* lmarshal.c
* A Lua library for serializing and deserializing Lua values
* Richard Hundt <richardhundt@gmail.com>, Eluna Lua Engine <http://emudevs.com/>
*
* License: MIT
*
* Copyright (c) 2010 Richard Hundt
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#define MAR_TREF 1
#define MAR_TVAL 2
#define MAR_TUSR 3
#define MAR_CHR 1
#define MAR_I32 4
#define MAR_I64 8
#define MAR_MAGIC 0x8f
#define SEEN_IDX 3
#define MAR_ENV_IDX_KEY "E"
#define MAR_NUPS_IDX_KEY "n"
typedef struct mar_Buffer {
size_t size;
size_t seek;
size_t head;
char* data;
} mar_Buffer;
static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx);
static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx);
static void buf_init(lua_State *L, mar_Buffer *buf)
{
buf->size = 128;
buf->seek = 0;
buf->head = 0;
if (!(buf->data = (char*)malloc(buf->size))) luaL_error(L, "Out of memory!");
}
static void buf_done(lua_State* L, mar_Buffer *buf)
{
free(buf->data);
}
static int buf_write(lua_State* L, const char* str, size_t len, mar_Buffer *buf)
{
if (len > UINT32_MAX) luaL_error(L, "buffer too long");
if (buf->size - buf->head < len) {
size_t new_size = buf->size << 1;
size_t cur_head = buf->head;
while (new_size - cur_head <= len) {
new_size = new_size << 1;
}
if (!(buf->data = (char*)realloc(buf->data, new_size))) {
luaL_error(L, "Out of memory!");
}
buf->size = new_size;
}
memcpy(&buf->data[buf->head], str, len);
buf->head += len;
return 0;
}
static const char* buf_read(lua_State *L, mar_Buffer *buf, size_t *len)
{
if (buf->seek < buf->head) {
buf->seek = buf->head;
*len = buf->seek;
return buf->data;
}
*len = 0;
return NULL;
}
static void mar_encode_value(lua_State *L, mar_Buffer *buf, int val, size_t *idx)
{
size_t l;
int val_type = lua_type(L, val);
lua_pushvalue(L, val);
buf_write(L, (const char*)&val_type, MAR_CHR, buf);
switch (val_type) {
case LUA_TBOOLEAN: {
int int_val = lua_toboolean(L, -1);
buf_write(L, (const char*)&int_val, MAR_CHR, buf);
break;
}
case LUA_TSTRING: {
const char *str_val = lua_tolstring(L, -1, &l);
buf_write(L, (const char*)&l, MAR_I32, buf);
buf_write(L, str_val, l, buf);
break;
}
case LUA_TNUMBER: {
lua_Number num_val = lua_tonumber(L, -1);
buf_write(L, (const char*)&num_val, MAR_I64, buf);
break;
}
case LUA_TTABLE: {
int tag, ref;
lua_pushvalue(L, -1);
lua_rawget(L, SEEN_IDX);
if (!lua_isnil(L, -1)) {
ref = lua_tointeger(L, -1);
tag = MAR_TREF;
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&ref, MAR_I32, buf);
lua_pop(L, 1);
}
else {
mar_Buffer rec_buf;
lua_pop(L, 1); /* pop nil */
if (luaL_getmetafield(L, -1, "__persist")) {
tag = MAR_TUSR;
lua_pushvalue(L, -2); /* self */
lua_call(L, 1, 1);
if (!lua_isfunction(L, -1)) {
luaL_error(L, "__persist must return a function");
}
lua_remove(L, -2); /* __persist */
lua_newtable(L);
lua_pushvalue(L, -2); /* callback */
lua_rawseti(L, -2, 1);
buf_init(L, &rec_buf);
mar_encode_table(L, &rec_buf, idx);
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&rec_buf.head, MAR_I32, buf);
buf_write(L, rec_buf.data, rec_buf.head, buf);
buf_done(L, &rec_buf);
lua_pop(L, 1);
}
else {
tag = MAR_TVAL;
lua_pushvalue(L, -1);
lua_pushinteger(L, (*idx)++);
lua_rawset(L, SEEN_IDX);
lua_pushvalue(L, -1);
buf_init(L, &rec_buf);
mar_encode_table(L, &rec_buf, idx);
lua_pop(L, 1);
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&rec_buf.head, MAR_I32, buf);
buf_write(L, rec_buf.data,rec_buf.head, buf);
buf_done(L, &rec_buf);
}
}
break;
}
case LUA_TFUNCTION: {
int tag, ref;
lua_pushvalue(L, -1);
lua_rawget(L, SEEN_IDX);
if (!lua_isnil(L, -1)) {
ref = lua_tointeger(L, -1);
tag = MAR_TREF;
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&ref, MAR_I32, buf);
lua_pop(L, 1);
}
else {
mar_Buffer rec_buf;
unsigned int i;
lua_Debug ar;
lua_pop(L, 1); /* pop nil */
lua_pushvalue(L, -1);
lua_getinfo(L, ">nuS", &ar);
if (ar.what[0] != 'L') {
luaL_error(L, "attempt to persist a C function '%s'", ar.name);
}
tag = MAR_TVAL;
lua_pushvalue(L, -1);
lua_pushinteger(L, (*idx)++);
lua_rawset(L, SEEN_IDX);
lua_pushvalue(L, -1);
buf_init(L, &rec_buf);
lua_dump(L, (lua_Writer)buf_write, &rec_buf);
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&rec_buf.head, MAR_I32, buf);
buf_write(L, rec_buf.data, rec_buf.head, buf);
buf_done(L, &rec_buf);
lua_pop(L, 1);
lua_newtable(L);
for (i = 1; i <= ar.nups; i++) {
const char* upvalue_name = lua_getupvalue(L, -2, i);
if (strcmp("_ENV", upvalue_name) == 0) {
lua_pop(L, 1);
// Mark where _ENV is expected.
lua_pushstring(L, MAR_ENV_IDX_KEY);
lua_pushinteger(L, i);
lua_rawset(L, -3);
}
else {
lua_rawseti(L, -2, i);
}
}
lua_pushstring(L, MAR_NUPS_IDX_KEY);
lua_pushnumber(L, ar.nups);
lua_rawset(L, -3);
buf_init(L, &rec_buf);
mar_encode_table(L, &rec_buf, idx);
buf_write(L, (const char*)&rec_buf.head, MAR_I32, buf);
buf_write(L, rec_buf.data, rec_buf.head, buf);
buf_done(L, &rec_buf);
lua_pop(L, 1);
}
break;
}
case LUA_TUSERDATA: {
int tag, ref;
lua_pushvalue(L, -1);
lua_rawget(L, SEEN_IDX);
if (!lua_isnil(L, -1)) {
ref = lua_tointeger(L, -1);
tag = MAR_TREF;
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&ref, MAR_I32, buf);
lua_pop(L, 1);
}
else {
mar_Buffer rec_buf;
lua_pop(L, 1); /* pop nil */
if (luaL_getmetafield(L, -1, "__persist")) {
tag = MAR_TUSR;
lua_pushvalue(L, -2);
lua_pushinteger(L, (*idx)++);
lua_rawset(L, SEEN_IDX);
lua_pushvalue(L, -2);
lua_call(L, 1, 1);
if (!lua_isfunction(L, -1)) {
luaL_error(L, "__persist must return a function");
}
lua_newtable(L);
lua_pushvalue(L, -2);
lua_rawseti(L, -2, 1);
lua_remove(L, -2);
buf_init(L, &rec_buf);
mar_encode_table(L, &rec_buf, idx);
buf_write(L, (const char*)&tag, MAR_CHR, buf);
buf_write(L, (const char*)&rec_buf.head, MAR_I32, buf);
buf_write(L, rec_buf.data, rec_buf.head, buf);
buf_done(L, &rec_buf);
}
else {
luaL_error(L, "attempt to encode userdata (no __persist hook)");
}
lua_pop(L, 1);
}
break;
}
case LUA_TNIL: break;
default:
luaL_error(L, "invalid value type (%s)", lua_typename(L, val_type));
}
lua_pop(L, 1);
}
static int mar_encode_table(lua_State *L, mar_Buffer *buf, size_t *idx)
{
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
mar_encode_value(L, buf, -2, idx);
mar_encode_value(L, buf, -1, idx);
lua_pop(L, 1);
}
return 1;
}
#define mar_incr_ptr(l) \
if (((*p)-buf)+(ptrdiff_t)(l) > (ptrdiff_t)len) luaL_error(L, "bad code"); (*p) += (l);
#define mar_next_len(l,T) \
if (((*p)-buf)+(ptrdiff_t)sizeof(T) > (ptrdiff_t)len) luaL_error(L, "bad code"); \
l = *(T*)*p; (*p) += sizeof(T);
static void mar_decode_value
(lua_State *L, const char *buf, size_t len, const char **p, size_t *idx)
{
size_t l;
char val_type = **p;
mar_incr_ptr(MAR_CHR);
switch (val_type) {
case LUA_TBOOLEAN:
lua_pushboolean(L, *(char*)*p);
mar_incr_ptr(MAR_CHR);
break;
case LUA_TNUMBER:
lua_pushnumber(L, *(lua_Number*)*p);
mar_incr_ptr(MAR_I64);
break;
case LUA_TSTRING:
mar_next_len(l, uint32_t);
lua_pushlstring(L, *p, l);
mar_incr_ptr(l);
break;
case LUA_TTABLE: {
char tag = *(char*)*p;
mar_incr_ptr(MAR_CHR);
if (tag == MAR_TREF) {
int ref;
mar_next_len(ref, int);
lua_rawgeti(L, SEEN_IDX, ref);
}
else if (tag == MAR_TVAL) {
mar_next_len(l, uint32_t);
lua_newtable(L);
lua_pushvalue(L, -1);
lua_rawseti(L, SEEN_IDX, (*idx)++);
mar_decode_table(L, *p, l, idx);
mar_incr_ptr(l);
}
else if (tag == MAR_TUSR) {
mar_next_len(l, uint32_t);
lua_newtable(L);
mar_decode_table(L, *p, l, idx);
lua_rawgeti(L, -1, 1);
lua_call(L, 0, 1);
lua_remove(L, -2);
lua_pushvalue(L, -1);
lua_rawseti(L, SEEN_IDX, (*idx)++);
mar_incr_ptr(l);
}
else {
luaL_error(L, "bad encoded data");
}
break;
}
case LUA_TFUNCTION: {
unsigned int nups;
unsigned int i;
mar_Buffer dec_buf;
char tag = *(char*)*p;
mar_incr_ptr(1);
if (tag == MAR_TREF) {
int ref;
mar_next_len(ref, int);
lua_rawgeti(L, SEEN_IDX, ref);
}
else {
mar_next_len(l, uint32_t);
dec_buf.data = (char*)*p;
dec_buf.size = l;
dec_buf.head = l;
dec_buf.seek = 0;
lua_load(L, (lua_Reader)buf_read, &dec_buf, "=marshal", NULL);
mar_incr_ptr(l);
lua_pushvalue(L, -1);
lua_rawseti(L, SEEN_IDX, (*idx)++);
mar_next_len(l, uint32_t);
lua_newtable(L);
mar_decode_table(L, *p, l, idx);
lua_pushstring(L, MAR_ENV_IDX_KEY);
lua_rawget(L, -2);
if (lua_isnumber(L, -1)) {
lua_pushglobaltable(L);
lua_rawset(L, -3);
}
else {
lua_pop(L, 1);
}
lua_pushstring(L, MAR_NUPS_IDX_KEY);
lua_rawget(L, -2);
nups = luaL_checknumber(L, -1);
lua_pop(L, 1);
for (i = 1; i <= nups; i++) {
lua_rawgeti(L, -1, i);
lua_setupvalue(L, -3, i);
}
lua_pop(L, 1);
mar_incr_ptr(l);
}
break;
}
case LUA_TUSERDATA: {
char tag = *(char*)*p;
mar_incr_ptr(MAR_CHR);
if (tag == MAR_TREF) {
int ref;
mar_next_len(ref, int);
lua_rawgeti(L, SEEN_IDX, ref);
}
else if (tag == MAR_TUSR) {
mar_next_len(l, uint32_t);
lua_newtable(L);
mar_decode_table(L, *p, l, idx);
lua_rawgeti(L, -1, 1);
lua_call(L, 0, 1);
lua_remove(L, -2);
lua_pushvalue(L, -1);
lua_rawseti(L, SEEN_IDX, (*idx)++);
mar_incr_ptr(l);
}
else { /* tag == MAR_TVAL */
lua_pushnil(L);
}
break;
}
case LUA_TNIL:
case LUA_TTHREAD:
lua_pushnil(L);
break;
default:
luaL_error(L, "bad code");
}
}
static int mar_decode_table(lua_State *L, const char* buf, size_t len, size_t *idx)
{
const char* p;
p = buf;
while (p - buf < (ptrdiff_t)len) {
mar_decode_value(L, buf, len, &p, idx);
mar_decode_value(L, buf, len, &p, idx);
lua_settable(L, -3);
}
return 1;
}
int mar_encode(lua_State* L)
{
const unsigned char m = MAR_MAGIC;
size_t idx, len;
mar_Buffer buf;
if (lua_isnone(L, 1)) {
lua_pushnil(L);
}
if (lua_isnoneornil(L, 2)) {
lua_newtable(L);
}
else if (!lua_istable(L, 2)) {
luaL_error(L, "bad argument #2 to encode (expected table)");
}
lua_settop(L, 2);
len = lua_rawlen(L, 2);
lua_newtable(L);
for (idx = 1; idx <= len; idx++) {
lua_rawgeti(L, 2, idx);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
continue;
}
lua_pushinteger(L, idx);
lua_rawset(L, SEEN_IDX);
}
lua_pushvalue(L, 1);
buf_init(L, &buf);
buf_write(L, (const char*)&m, 1, &buf);
mar_encode_value(L, &buf, -1, &idx);
lua_pop(L, 1);
lua_pushlstring(L, buf.data, buf.head);
buf_done(L, &buf);
lua_remove(L, SEEN_IDX);
return 1;
}
int mar_decode(lua_State* L)
{
size_t l, idx, len;
const char *p;
const char *s = luaL_checklstring(L, 1, &l);
if (l < 1) luaL_error(L, "bad header");
if (*(unsigned char *)s++ != MAR_MAGIC) luaL_error(L, "bad magic");
l -= 1;
if (lua_isnoneornil(L, 2)) {
lua_newtable(L);
}
else if (!lua_istable(L, 2)) {
luaL_error(L, "bad argument #2 to decode (expected table)");
}
lua_settop(L, 2);
len = lua_rawlen(L, 2);
lua_newtable(L);
for (idx = 1; idx <= len; idx++) {
lua_rawgeti(L, 2, idx);
lua_rawseti(L, SEEN_IDX, idx);
}
p = s;
mar_decode_value(L, s, l, &p, &idx);
lua_remove(L, SEEN_IDX);
lua_remove(L, 2);
return 1;
}
int mar_clone(lua_State* L)
{
mar_encode(L);
lua_replace(L, 1);
mar_decode(L);
return 1;
}
static const luaL_Reg R[] =
{
{"encode", mar_encode},
{"decode", mar_decode},
{"clone", mar_clone},
{NULL, NULL}
};
int luaopen_marshal(lua_State *L)
{
lua_newtable(L);
luaL_setfuncs(L, R, 0);
return 1;
}

12
lmarshal.h Normal file
View File

@@ -0,0 +1,12 @@
/*
* Copyright (C) 2015 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
*/
extern "C" {
#include "lua.h"
}
int mar_encode(lua_State* L);
int mar_decode(lua_State* L);