Skip to content

Commit

Permalink
Allow scripting of instances w/ persistent data
Browse files Browse the repository at this point in the history
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
Patman64 authored and Rochet2 committed Jul 31, 2015
1 parent d57ca13 commit 31470c2
Showing 14 changed files with 1,564 additions and 25 deletions.
14 changes: 8 additions & 6 deletions BindingMap.h
Original file line number Diff line number Diff line change
@@ -232,12 +232,13 @@ struct EventKey
* (CreatureEvents, GameObjectEvents, etc.).
*/
template <typename T>
struct EntryKey : public EventKey<T>
struct EntryKey
{
T event_id;
uint32 entry;

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

UniqueObjectKey(T event_type, uint64 guid, uint32 instance_id) :
EventKey<T>(event_type),
UniqueObjectKey(T event_id, uint64 guid, uint32 instance_id) :
event_id(event_id),
guid(guid),
instance_id(instance_id)
{}
227 changes: 227 additions & 0 deletions ElunaInstanceAI.cpp
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Oops, something went wrong.

0 comments on commit 31470c2

Please sign in to comment.