Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for working with entity lumps #1673

Merged
merged 12 commits into from
Sep 5, 2022
3 changes: 3 additions & 0 deletions bridge/include/LogicProvider.h
Original file line number Diff line number Diff line change
@@ -73,6 +73,9 @@ struct sm_logic_t
void (*FreeCellArray)(ICellArray *arr);
void * (*FromPseudoAddress)(uint32_t pseudoAddr);
uint32_t (*ToPseudoAddress)(void *addr);
void (*SetEntityLumpWritable)(bool writable);
bool (*ParseEntityLumpString)(const char *entityString, int &status, size_t &position);
const char * (*GetEntityLumpString)();
IScriptManager *scripts;
IShareSys *sharesys;
IExtensionSys *extsys;
2 changes: 2 additions & 0 deletions core/logic/AMBuilder
Original file line number Diff line number Diff line change
@@ -84,6 +84,8 @@ for cxx in builder.targets:
'smn_halflife.cpp',
'FrameIterator.cpp',
'DatabaseConfBuilder.cpp',
'LumpManager.cpp',
'smn_entitylump.cpp',
]

if binary.compiler.target.arch == 'x86_64':
133 changes: 133 additions & 0 deletions core/logic/LumpManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* vim: set ts=4 :
* =============================================================================
* Entity Lump Manager
* Copyright (C) 2021-2022 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/

#include "LumpManager.h"

#include <iomanip>
#include <sstream>

EntityLumpParseResult::operator bool() const {
return m_Status == Status_OK;
}

EntityLumpParseResult EntityLumpManager::Parse(const char* pMapEntities) {
m_Entities.clear();

std::istringstream mapEntities(pMapEntities);

for (;;) {
std::string token;
mapEntities >> std::ws >> token >> std::ws;

// Assert that we're at the start of a new block, otherwise we're done parsing
if (token != "{") {
if (token == "\0") {
break;
} else {
return EntityLumpParseResult {
Status_UnexpectedChar, mapEntities.tellg()
};
}
}

/**
* Parse key / value pairs until we reach a closing brace. We currently assume there
* are only quoted keys / values up to the next closing brace.
*
* The SDK suggests that there are cases that could use non-quoted symbols and nested
* braces (`shared/mapentities_shared.cpp::MapEntity_ParseToken`), but I haven't seen
* those in practice.
*/
EntityLumpEntry entry;
while (mapEntities.peek() != '}') {
std::string key, value;

if (mapEntities.peek() != '"') {
return EntityLumpParseResult {
Status_UnexpectedChar, mapEntities.tellg()
};
}
mapEntities >> quoted(key) >> std::ws;

if (mapEntities.peek() != '"') {
return EntityLumpParseResult {
Status_UnexpectedChar, mapEntities.tellg()
};
}
mapEntities >> quoted(value) >> std::ws;

entry.emplace_back(key, value);
}
mapEntities.get();
m_Entities.push_back(std::make_shared<EntityLumpEntry>(entry));
}

return EntityLumpParseResult{};
}

std::string EntityLumpManager::Dump() {
std::ostringstream stream;
for (const auto& entry : m_Entities) {
// ignore empty entries
if (entry->empty()) {
continue;
}
stream << "{\n";
for (const auto& pair : *entry) {
stream << '"' << pair.first << "\" \"" << pair.second << '"' << '\n';
}
stream << "}\n";
}
return stream.str();
}

std::weak_ptr<EntityLumpEntry> EntityLumpManager::Get(size_t index) {
return m_Entities[index];
}

void EntityLumpManager::Erase(size_t index) {
m_Entities.erase(m_Entities.begin() + index);
}

void EntityLumpManager::Insert(size_t index) {
m_Entities.emplace(m_Entities.begin() + index, std::make_shared<EntityLumpEntry>());
}

size_t EntityLumpManager::Append() {
return std::distance(
m_Entities.begin(),
m_Entities.emplace(m_Entities.end(), std::make_shared<EntityLumpEntry>())
);
}

size_t EntityLumpManager::Length() {
return m_Entities.size();
}
116 changes: 116 additions & 0 deletions core/logic/LumpManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* vim: set ts=4 :
* =============================================================================
* Entity Lump Manager
* Copyright (C) 2021-2022 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/

#ifndef _INCLUDE_LUMPMANAGER_H_
#define _INCLUDE_LUMPMANAGER_H_

#include <vector>
#include <memory>
#include <string>

/**
* Entity lump manager. Provides a list that stores a list of key / value pairs and the
* functionality to (de)serialize it from / to an entity string.
* This file and its corresponding .cpp should be compilable independently of SourceMod;
* the SourceMod interop is located within smn_entitylump.
*
* @file lumpmanager.h
* @brief Class definition for object that parses lumps.
*/

/**
* @brief A container of key / value pairs.
*/
using EntityLumpEntry = std::vector<std::pair<std::string, std::string>>;

enum EntityLumpParseStatus {
Status_OK,
Status_UnexpectedChar,
};

/**
* @brief Result of parsing an entity lump. On a parse error, m_Status is not Status_OK and
* m_Position indicates the offset within the string that caused the parse error.
*/
struct EntityLumpParseResult {
EntityLumpParseStatus m_Status;
std::streamoff m_Position;

operator bool() const;
const char* Description() const;
};

/**
* @brief Manages entity lump entries.
*/
class EntityLumpManager
{
public:
/**
* @brief Parses the map entities string into an internal representation.
*/
EntityLumpParseResult Parse(const char* pMapEntities);

/**
* @brief Dumps the current internal representation out to an std::string.
*/
std::string Dump();

/**
* @brief Returns a weak reference to an EntityLumpEntry. Used for handles on the scripting side.
*/
std::weak_ptr<EntityLumpEntry> Get(size_t index);

/**
* @brief Removes an EntityLumpEntry at the given index, shifting down all entries after it by one.
*/
void Erase(size_t index);

/**
* @brief Inserts a new EntityLumpEntry at the given index, shifting up the entries previously at the index and after it up by one.
*/
void Insert(size_t index);

/**
* @brief Adds a new EntityLumpEntry to the end. Returns the index of the entry.
*/
size_t Append();

/**
* @brief Returns the number of EntityLumpEntry items in the list.
*/
size_t Length();

private:
std::vector<std::shared_ptr<EntityLumpEntry>> m_Entities;
};

#endif // _INCLUDE_LUMPMANAGER_H_
33 changes: 33 additions & 0 deletions core/logic/common_logic.cpp
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
#include "LibrarySys.h"
#include "RootConsoleMenu.h"
#include "CellArray.h"
#include "smn_entitylump.h"
#include <bridge/include/BridgeAPI.h>
#include <bridge/include/IProviderCallbacks.h>

@@ -135,6 +136,35 @@ static uint32_t ToPseudoAddress(void *addr)
#endif
}

static void SetEntityLumpWritable(bool writable)
{
g_bLumpAvailableForWriting = writable;

// write-lock causes the map entities to be serialized out to string
if (!writable)
{
g_strMapEntities = lumpmanager->Dump();
}
}

static bool ParseEntityLumpString(const char *pMapEntities, int &status, size_t &position)
{
EntityLumpParseResult result = lumpmanager->Parse(pMapEntities);
status = static_cast<int>(result.m_Status);
position = static_cast<size_t>(result.m_Position);
return result;
}

// returns nullptr if the original lump failed to parse
static const char* GetEntityLumpString()
{
if (g_strMapEntities.empty())
{
return nullptr;
}
return g_strMapEntities.c_str();
}

// Defined in smn_filesystem.cpp.
extern bool OnLogPrint(const char *msg);

@@ -170,6 +200,9 @@ static sm_logic_t logic =
CellArray::Free,
FromPseudoAddress,
ToPseudoAddress,
SetEntityLumpWritable,
ParseEntityLumpString,
GetEntityLumpString,
&g_PluginSys,
&g_ShareSys,
&g_Extensions,
367 changes: 367 additions & 0 deletions core/logic/smn_entitylump.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
/**
* vim: set ts=4 :
* =============================================================================
* Entity Lump Manager
* Copyright (C) 2021-2022 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/

#include "HandleSys.h"
#include "common_logic.h"

#include "LumpManager.h"

#include <algorithm>

HandleType_t g_EntityLumpEntryType;

std::string g_strMapEntities;
bool g_bLumpAvailableForWriting = false;

static EntityLumpManager s_LumpManager;
EntityLumpManager *lumpmanager = &s_LumpManager;

class LumpManagerNatives :
public IHandleTypeDispatch,
public SMGlobalClass
{
public: //SMGlobalClass
void OnSourceModAllInitialized()
{
g_EntityLumpEntryType = handlesys->CreateType("EntityLumpEntry", this, 0, NULL, NULL, g_pCoreIdent, NULL);
}
void OnSourceModShutdown()
{
handlesys->RemoveType(g_EntityLumpEntryType, g_pCoreIdent);
}
public: //IHandleTypeDispatch
void OnHandleDestroy(HandleType_t type, void* object)
{
if (type == g_EntityLumpEntryType)
{
delete reinterpret_cast<std::weak_ptr<EntityLumpEntry>*>(object);
}
}
};

static LumpManagerNatives s_LumpManagerNatives;

cell_t sm_LumpManagerGet(IPluginContext *pContext, const cell_t *params) {
int index = params[1];
if (index < 0 || index >= static_cast<int>(lumpmanager->Length())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

std::weak_ptr<EntityLumpEntry>* pReference = new std::weak_ptr<EntityLumpEntry>;
*pReference = lumpmanager->Get(index);

return handlesys->CreateHandle(g_EntityLumpEntryType, pReference,
pContext->GetIdentity(), g_pCoreIdent, NULL);
}

cell_t sm_LumpManagerErase(IPluginContext *pContext, const cell_t *params) {
if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLump.Erase() outside of OnMapInit");
}

int index = params[1];
if (index < 0 || index >= static_cast<int>(lumpmanager->Length())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

lumpmanager->Erase(index);
return 0;
}

cell_t sm_LumpManagerInsert(IPluginContext *pContext, const cell_t *params) {
if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLump.Insert() outside of OnMapInit");
}

int index = params[1];
if (index < 0 || index > static_cast<int>(lumpmanager->Length())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

lumpmanager->Insert(index);
return 0;
}

cell_t sm_LumpManagerAppend(IPluginContext *pContext, const cell_t *params) {
if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLump.Append() outside of OnMapInit");
}
return lumpmanager->Append();
}

cell_t sm_LumpManagerLength(IPluginContext *pContext, const cell_t *params) {
return lumpmanager->Length();
}

cell_t sm_LumpEntryGet(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

auto entry = entryref->lock();

int index = params[2];
if (index < 0 || index >= static_cast<int>(entry->size())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

const auto& pair = (*entry)[index];

size_t nBytes;
pContext->StringToLocalUTF8(params[3], params[4], pair.first.c_str(), &nBytes);
pContext->StringToLocalUTF8(params[5], params[6], pair.second.c_str(), &nBytes);

return 0;
}

cell_t sm_LumpEntryUpdate(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLumpEntry.Update() outside of OnMapInit");
}

auto entry = entryref->lock();

int index = params[2];
if (index < 0 || index >= static_cast<int>(entry->size())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

char *key, *value;
pContext->LocalToStringNULL(params[3], &key);
pContext->LocalToStringNULL(params[4], &value);

auto& pair = (*entry)[index];
if (key != nullptr) {
pair.first = key;
}
if (value != nullptr) {
pair.second = value;
}

return 0;
}

cell_t sm_LumpEntryInsert(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLumpEntry.Insert() outside of OnMapInit");
}

auto entry = entryref->lock();

int index = params[2];
if (index < 0 || index > static_cast<int>(entry->size())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

char *key, *value;
pContext->LocalToString(params[3], &key);
pContext->LocalToString(params[4], &value);

entry->emplace(entry->begin() + index, key, value);

return 0;
}

cell_t sm_LumpEntryErase(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLumpEntry.Erase() outside of OnMapInit");
}

auto entry = entryref->lock();

int index = params[2];
if (index < 0 || index >= static_cast<int>(entry->size())) {
return pContext->ThrowNativeError("Invalid index %d", index);
}

entry->erase(entry->begin() + index);

return 0;
}

cell_t sm_LumpEntryAppend(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

if (!g_bLumpAvailableForWriting) {
return pContext->ThrowNativeError("Cannot use EntityLumpEntry.Append() outside of OnMapInit");
}

auto entry = entryref->lock();

char *key, *value;
pContext->LocalToString(params[2], &key);
pContext->LocalToString(params[3], &value);

entry->emplace_back(key, value);

return 0;
}

cell_t sm_LumpEntryFindKey(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

// start from the index after the current one
int start = params[3] + 1;

auto entry = entryref->lock();

if (start < 0 || start >= static_cast<int>(entry->size())) {
return -1;
}

char *key;
pContext->LocalToString(params[2], &key);

auto matches_key = [&key](std::pair<std::string,std::string> pair) {
return pair.first == key;
};

auto result = std::find_if(entry->begin() + start, entry->end(), matches_key);

if (result == entry->end()) {
return -1;
}
return std::distance(entry->begin(), result);
}

cell_t sm_LumpEntryLength(IPluginContext *pContext, const cell_t *params) {
HandleSecurity sec(pContext->GetIdentity(), g_pCoreIdent);
HandleError err;

Handle_t hndl = static_cast<Handle_t>(params[1]);

std::weak_ptr<EntityLumpEntry>* entryref = nullptr;
if ((err = handlesys->ReadHandle(hndl, g_EntityLumpEntryType, &sec, (void**) &entryref)) != HandleError_None) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (error: %d)", hndl, err);
}

if (entryref->expired()) {
return pContext->ThrowNativeError("Invalid EntityLumpEntry handle %x (reference expired)", hndl);
}

auto entry = entryref->lock();
return entry->size();
}

REGISTER_NATIVES(entityLumpNatives)
{
{ "EntityLump.Get", sm_LumpManagerGet },
{ "EntityLump.Erase", sm_LumpManagerErase },
{ "EntityLump.Insert", sm_LumpManagerInsert },
{ "EntityLump.Append", sm_LumpManagerAppend },
{ "EntityLump.Length", sm_LumpManagerLength },

{ "EntityLumpEntry.Get", sm_LumpEntryGet },
{ "EntityLumpEntry.Update", sm_LumpEntryUpdate },
{ "EntityLumpEntry.Insert", sm_LumpEntryInsert },
{ "EntityLumpEntry.Erase", sm_LumpEntryErase },
{ "EntityLumpEntry.Append", sm_LumpEntryAppend },
{ "EntityLumpEntry.FindKey", sm_LumpEntryFindKey },
{ "EntityLumpEntry.Length.get", sm_LumpEntryLength },

{NULL, NULL}
};
44 changes: 44 additions & 0 deletions core/logic/smn_entitylump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2022-2022 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/

#ifndef _INCLUDE_SOURCEMOD_ENTITYLUMP_H_
#define _INCLUDE_SOURCEMOD_ENTITYLUMP_H_

#include <IHandleSys.h>
#include "LumpManager.h"

using namespace SourceMod;

extern std::string g_strMapEntities;
extern bool g_bLumpAvailableForWriting;
extern EntityLumpManager *lumpmanager;

#endif // _INCLUDE_SOURCEMOD_ENTITYLUMP_H_
27 changes: 26 additions & 1 deletion core/sourcemod.cpp
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false);
SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool);
SH_DECL_HOOK1_void(IServerGameDLL, Think, SH_NOATTRIB, false, bool);
SH_DECL_HOOK1_void(IVEngineServer, ServerCommand, SH_NOATTRIB, false, const char *);
SH_DECL_HOOK0(IVEngineServer, GetMapEntitiesString, SH_NOATTRIB, 0, const char *);

SourceModBase g_SourceMod;

@@ -278,6 +279,7 @@ bool SourceModBase::InitializeSourceMod(char *error, size_t maxlength, bool late

/* Hook this now so we can detect startup without calling StartSourceMod() */
SH_ADD_HOOK(IServerGameDLL, LevelInit, gamedll, SH_MEMBER(this, &SourceModBase::LevelInit), false);
SH_ADD_HOOK(IVEngineServer, GetMapEntitiesString, engine, SH_MEMBER(this, &SourceModBase::GetMapEntitiesString), false);

/* Only load if we're not late */
if (!late)
@@ -416,10 +418,32 @@ bool SourceModBase::LevelInit(char const *pMapName, char const *pMapEntities, ch

g_LevelEndBarrier = true;

int parseError;
size_t position;
bool success = logicore.ParseEntityLumpString(pMapEntities, parseError, position);

logicore.SetEntityLumpWritable(true);
g_pOnMapInit->PushString(pMapName);
g_pOnMapInit->Execute();
logicore.SetEntityLumpWritable(false);

RETURN_META_VALUE(MRES_IGNORED, true);
if (!success)
{
logger->LogError("Map entity lump parsing for %s failed with error code %d on position %d", pMapName, parseError, position);
RETURN_META_VALUE(MRES_IGNORED, true);
}

RETURN_META_VALUE_NEWPARAMS(MRES_HANDLED, true, &IServerGameDLL::LevelInit, (pMapName, logicore.GetEntityLumpString(), pOldLevel, pLandmarkName, loadGame, background));
}

const char *SourceModBase::GetMapEntitiesString()
{
const char *pNewMapEntities = logicore.GetEntityLumpString();
if (pNewMapEntities != nullptr)
{
RETURN_META_VALUE(MRES_SUPERCEDE, pNewMapEntities);
}
RETURN_META_VALUE(MRES_IGNORED, NULL);
}

void SourceModBase::LevelShutdown()
@@ -534,6 +558,7 @@ void SourceModBase::CloseSourceMod()
return;

SH_REMOVE_HOOK(IServerGameDLL, LevelInit, gamedll, SH_MEMBER(this, &SourceModBase::LevelInit), false);
SH_REMOVE_HOOK(IVEngineServer, GetMapEntitiesString, engine, SH_MEMBER(this, &SourceModBase::GetMapEntitiesString), false);

if (g_Loaded)
{
1 change: 1 addition & 0 deletions core/sourcemod.h
Original file line number Diff line number Diff line change
@@ -140,6 +140,7 @@ class SourceModBase :
private:
void ShutdownServices();
private:
const char* GetMapEntitiesString();
char m_SMBaseDir[PLATFORM_MAX_PATH];
char m_SMRelDir[PLATFORM_MAX_PATH];
char m_ModDir[32];
118 changes: 118 additions & 0 deletions plugins/include/entitylump.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#if defined _entitylump_included
#endinput
#endif

#define _entitylump_included

/**
* A handle to a weakref of an ordered list of key / value pairs; any write operations on this handle will affect the resulting map entity string.
* If the entry in the EntityLump is removed, the handle will error on all operations.
* (The handle will remain valid on the scripting side, and will still need to be deleted.)
*/
methodmap EntityLumpEntry < Handle {
/**
* Copies the key / value at the given index into buffers.
*
* @error Index is out of bounds.
*/
public native void Get(int index, char[] keybuf = "", int keylen = 0, char[] valbuf = "", int vallen = 0);

/**
* Updates the key / value pair at the given index.
* NULL_STRING may be used to preserve the existing value.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public native void Update(int index, const char[] key = NULL_STRING, const char[] value = NULL_STRING);

/**
* Inserts a new key / value pair at the given index, shifting the pair at that index and beyond up.
* If EntityLumpEntry.Length is passed in, this is an append operation.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public native void Insert(int index, const char[] key, const char[] value);

/**
* Removes the key / value pair at the given index, shifting all entries past it down.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public native void Erase(int index);

/**
* Inserts a new key / value pair at the end.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public native void Append(const char[] key, const char[] value);

/**
* Returns the first index after `start` with the matching key, or -1 if not found.
*/
public native int FindKey(const char[] key, int start = -1);

/**
* Returns the first index after `start` with the matching key, or -1 if not found.
* This also copies the value from that index into the given buffer.
*
* @error `start` is out of bounds and not -1.
*/
public int GetNextKey(const char[] key, char[] buffer, int maxlen, int start = -1) {
int result = this.FindKey(key, start);
if (result != -1) {
this.Get(result, .valbuf = buffer, .vallen = maxlen);
} else {
buffer[0] = '\0';
}
return result;
}

/**
* Returns the number of key / value pairs in the list.
*/
property int Length {
public native get();
}
};

/**
* A group of natives for a singleton entity lump.
* EntityLumpEntry instances are only available for writing during OnMapInit().
*/
methodmap EntityLump {
/**
* Returns the EntityLumpEntry at the given index.
* This handle should be freed by the calling plugin.
*
* @error Index is out of bounds.
*/
public static native EntityLumpEntry Get(int index);

/**
* Erases an EntityLumpEntry at the given index, shifting all entries past it down.
* Any handles referencing the erased EntityLumpEntry will throw on any operations aside from delete.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public static native void Erase(int index);

/**
* Inserts an empty EntityLumpEntry at the given index, shifting the existing entry and ones past it up.
*
* @error Index is out of bounds or entity lump is read-only.
*/
public static native void Insert(int index);

/**
* Creates an empty EntityLumpEntry, returning its index.
*
* @error Entity lump is read-only.
*/
public static native int Append();

/**
* Returns the number of entities currently in the lump.
*/
public static native int Length();
};
1 change: 1 addition & 0 deletions plugins/include/sourcemod.inc
Original file line number Diff line number Diff line change
@@ -76,6 +76,7 @@ struct Plugin
#include <commandfilters>
#include <nextmap>
#include <commandline>
#include <entitylump>

enum APLRes
{
125 changes: 125 additions & 0 deletions plugins/testsuite/entitylumptest.sp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#pragma semicolon 1
#include <sourcemod>

#include <sdktools>
#include <entitylump>

#pragma newdecls required

#define PLUGIN_VERSION "0.0.0"
public Plugin myinfo = {
name = "Entity Lump Core Native Test",
author = "nosoop",
description = "A port of the Level KeyValues entity test to the Entity Lump implementation in core SourceMod",
}

#define OUTPUT_NAME "OnCapTeam2"

public void OnMapInit() {
// set every area_time_to_cap value to 30
for (int i, n = EntityLump.Length(); i < n; i++) {
EntityLumpEntry entry = EntityLump.Get(i);

int ttc = entry.FindKey("area_time_to_cap");
if (ttc != -1) {
entry.Update(ttc, NULL_STRING, "30");
PrintToServer("Set time to cap for item %d to 30", i);
}

delete entry;
}
}

public void OnMapStart() {
int captureArea = FindEntityByClassname(-1, "trigger_capture_area");

if (!IsValidEntity(captureArea)) {
LogMessage("---- %s", "No capture area");
return;
}

int hammerid = GetEntProp(captureArea, Prop_Data, "m_iHammerID");
EntityLumpEntry entry = FindEntityLumpEntryByHammerID(hammerid);

if (!entry) {
return;
}

LogMessage("---- %s", "Found a trigger_capture_area with keys:");

for (int i, n = entry.Length; i < n; i++) {
char keyBuffer[128], valueBuffer[128];
entry.Get(i, keyBuffer, sizeof(keyBuffer), valueBuffer, sizeof(valueBuffer));

LogMessage("%s -> %s", keyBuffer, valueBuffer);
}

LogMessage("---- %s", "List of " ... OUTPUT_NAME ... " outputs:");
char outputString[256];
for (int k = -1; (k = entry.GetNextKey(OUTPUT_NAME, outputString, sizeof(outputString), k)) != -1;) {
char targetName[32], inputName[64], variantValue[32];
float delay;
int nFireCount;

ParseEntityOutputString(outputString, targetName, sizeof(targetName),
inputName, sizeof(inputName), variantValue, sizeof(variantValue),
delay, nFireCount);

LogMessage("target %s -> input %s (value %s, delay %.2f, refire %d)",
targetName, inputName, variantValue, delay, nFireCount);
}
delete entry;
}

/**
* Returns the first EntityLumpEntry with a matching hammerid.
*/
EntityLumpEntry FindEntityLumpEntryByHammerID(int hammerid) {
for (int i, n = EntityLump.Length(); i < n; i++) {
EntityLumpEntry entry = EntityLump.Get(i);

char value[32];
if (entry.GetNextKey("hammerid", value, sizeof(value)) != -1
&& StringToInt(value) == hammerid) {
return entry;
}
delete entry;
}
return null;
}

/**
* Parses an entity's output value (as formatted in the entity string).
* Refer to https://developer.valvesoftware.com/wiki/AddOutput for the format.
*
* @return True if the output string was successfully parsed, false if not.
*/
stock bool ParseEntityOutputString(const char[] output, char[] targetName, int targetNameLength,
char[] inputName, int inputNameLength, char[] variantValue, int variantValueLength,
float &delay, int &nFireCount) {
int delimiter;
char buffer[32];

{
// validate that we have something resembling an output string (four commas)
int i, c, nDelim;
while ((c = FindCharInString(output[i], ',')) != -1) {
nDelim++;
i += c + 1;
}
if (nDelim < 4) {
return false;
}
}

delimiter = SplitString(output, ",", targetName, targetNameLength);
delimiter += SplitString(output[delimiter], ",", inputName, inputNameLength);
delimiter += SplitString(output[delimiter], ",", variantValue, variantValueLength);

delimiter += SplitString(output[delimiter], ",", buffer, sizeof(buffer));
delay = StringToFloat(buffer);

nFireCount = StringToInt(output[delimiter]);

return true;
}
224 changes: 224 additions & 0 deletions tools/entlumpparser/AMBuildScript
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python:
import os, sys

# Simple extensions do not need to modify this file.

def ResolveEnvPath(env, folder):
if env in os.environ:
path = os.environ[env]
if os.path.isdir(path):
return path
return None

head = os.getcwd()
oldhead = None
while head != None and head != oldhead:
path = os.path.join(head, folder)
if os.path.isdir(path):
return path
oldhead = head
head, tail = os.path.split(head)

return None

def Normalize(path):
return os.path.abspath(os.path.normpath(path))

class ProgramConfig(object):
def __init__(self):
self.binaries = []
self.sm_root = None

@property
def tag(self):
if builder.options.debug == '1':
return 'Debug'
return 'Release'

def configure(self):
cxx = builder.DetectCompilers()

if builder.options.sm_path:
self.sm_root = builder.options.sm_path
if not self.sm_root or not os.path.isdir(self.sm_root):
raise Exception('Could not find a source copy of SourceMod')

if cxx.like('gcc'):
self.configure_gcc(cxx)
elif cxx.vendor == 'msvc':
self.configure_msvc(cxx)

# Optimization
if builder.options.opt == '1':
cxx.defines += ['NDEBUG']

# Debugging
if builder.options.debug == '1':
cxx.defines += ['DEBUG', '_DEBUG']

# Platform-specifics
if builder.target_platform == 'linux':
self.configure_linux(cxx)
elif builder.target_platform == 'mac':
self.configure_mac(cxx)
elif builder.target_platform == 'windows':
self.configure_windows(cxx)

# Finish up.
cxx.includes += [
os.path.join(self.sm_root, 'public'),
]

def configure_gcc(self, cxx):
cxx.defines += [
'stricmp=strcasecmp',
'_stricmp=strcasecmp',
'_snprintf=snprintf',
'_vsnprintf=vsnprintf',
'HAVE_STDINT_H',
'GNUC',
]
cxx.cflags += [
'-pipe',
'-fno-strict-aliasing',
'-Wall',
'-Werror',
'-Wno-unused',
'-Wno-switch',
'-Wno-array-bounds',
'-msse',
'-m32',
'-fvisibility=hidden',
]
cxx.cxxflags += [
'-std=c++14',
'-fno-exceptions',
'-fno-threadsafe-statics',
'-Wno-non-virtual-dtor',
'-Wno-overloaded-virtual',
'-fvisibility-inlines-hidden',
]
cxx.linkflags += ['-m32']

have_gcc = cxx.vendor == 'gcc'
have_clang = cxx.vendor == 'clang'
if cxx.version >= 'clang-3.9' or cxx.version == 'clang-3.4' or cxx.version > 'apple-clang-6.0':
cxx.cxxflags += ['-Wno-expansion-to-defined']
if cxx.version >= 'clang-3.6':
cxx.cxxflags += ['-Wno-inconsistent-missing-override']
if have_clang or (cxx.version >= 'gcc-4.6'):
cxx.cflags += ['-Wno-narrowing']
if have_clang or (cxx.version >= 'gcc-4.7'):
cxx.cxxflags += ['-Wno-delete-non-virtual-dtor']
if cxx.version >= 'gcc-4.8':
cxx.cflags += ['-Wno-unused-result']

if have_clang:
cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch']
if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4':
cxx.cxxflags += ['-Wno-deprecated-register']
else:
cxx.cxxflags += ['-Wno-deprecated']
cxx.cflags += ['-Wno-sometimes-uninitialized']

if have_gcc:
cxx.cflags += ['-mfpmath=sse']

if builder.options.opt == '1':
cxx.cflags += ['-O3']

def configure_msvc(self, cxx):
if builder.options.debug == '1':
cxx.cflags += ['/MTd']
cxx.linkflags += ['/NODEFAULTLIB:libcmt']
else:
cxx.cflags += ['/MT']
cxx.defines += [
'_CRT_SECURE_NO_DEPRECATE',
'_CRT_SECURE_NO_WARNINGS',
'_CRT_NONSTDC_NO_DEPRECATE',
'_ITERATOR_DEBUG_LEVEL=0',
]
cxx.cflags += [
'/W3',
]
cxx.cxxflags += [
'/EHsc',
'/GR-',
'/TP',
]
cxx.linkflags += [
'/MACHINE:X86',
'kernel32.lib',
'user32.lib',
'gdi32.lib',
'winspool.lib',
'comdlg32.lib',
'advapi32.lib',
'shell32.lib',
'ole32.lib',
'oleaut32.lib',
'uuid.lib',
'odbc32.lib',
'odbccp32.lib',
]

if builder.options.opt == '1':
cxx.cflags += ['/Ox', '/Zo']
cxx.linkflags += ['/OPT:ICF', '/OPT:REF']

if builder.options.debug == '1':
cxx.cflags += ['/Od', '/RTC1']

# This needs to be after our optimization flags which could otherwise disable it.
# Don't omit the frame pointer.
cxx.cflags += ['/Oy-']

def configure_linux(self, cxx):
cxx.defines += ['_LINUX', 'POSIX']
cxx.linkflags += ['-Wl,--exclude-libs,ALL', '-lm']
if cxx.vendor == 'gcc':
cxx.linkflags += ['-static-libgcc']
elif cxx.vendor == 'clang':
cxx.linkflags += ['-lgcc_eh']

def configure_mac(self, cxx):
cxx.defines += ['OSX', '_OSX', 'POSIX']
cxx.cflags += ['-mmacosx-version-min=10.5']
cxx.linkflags += [
'-mmacosx-version-min=10.5',
'-arch', 'i386',
'-lstdc++',
'-stdlib=libstdc++',
]
cxx.cxxflags += ['-stdlib=libstdc++']

def configure_windows(self, cxx):
cxx.defines += ['WIN32', '_WINDOWS']

def Program(self, context, name):
binary = context.compiler.Program(name)
if binary.compiler.like('msvc'):
binary.compiler.linkflags.append('/SUBSYSTEM:CONSOLE')
return binary

Tool = ProgramConfig()
Tool.configure()

# Add additional buildscripts here
BuildScripts = [
'AMBuilder',
]

binary = Tool.Program(builder, 'entlump_parser')
binary.sources += [
'console_main.cpp',
os.path.join(builder.options.sm_path, 'core', 'logic', 'LumpManager.cpp'),
]

binary.compiler.includes += [
os.path.join(builder.sourcePath),
os.path.join(builder.options.sm_path, 'core', 'logic'),
]

builder.Add(binary)
16 changes: 16 additions & 0 deletions tools/entlumpparser/AMBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python:
import os

binary = Tool.Program(builder, 'entlump_parser')

binary.sources += [
'console_main.cpp',
os.path.join(builder.options.sm_path, 'core', 'logic', 'LumpManager.cpp'),
]

binary.compiler.includes += [
os.path.join(builder.sourcePath),
os.path.join(builder.options.sm_path, 'core', 'logic'),
]

builder.Add(binary)
16 changes: 16 additions & 0 deletions tools/entlumpparser/configure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# vim: set sts=2 ts=8 sw=2 tw=99 et:
import sys
from ambuild2 import run

# Simple extensions do not need to modify this file.

builder = run.PrepareBuild(sourcePath = sys.path[0])

builder.options.add_option('--sm-path', type=str, dest='sm_path', default=None,
help='Path to SourceMod')
builder.options.add_option('--enable-debug', action='store_const', const='1', dest='debug',
help='Enable debugging symbols')
builder.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt',
help='Enable optimization')

builder.Configure()
22 changes: 22 additions & 0 deletions tools/entlumpparser/console_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <fstream>
#include <iostream>
#include <sstream>

#include "LumpManager.h"

int main(int argc, char *argv[]) {
if (argc < 2) {
std::cout << "Missing input file\n";
return 0;
}

const char* filepath = argv[1];

std::ifstream input(filepath, std::ios_base::binary);
std::string data((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());

EntityLumpManager lumpmgr;
lumpmgr.Parse(data.c_str());

std::cout << lumpmgr.Dump() << "\n";
}