mirror of
https://github.com/worldforge/cyphesis
synced 2026-08-13 12:26:04 -04:00
Classes under "rulesets" have been moved to "rules", and split up into futher subdirectories matching their library. As a result we can now better separate the python bindings, so that things that belongs to the simulation are separeted from things that belongs to the ai.
502 lines
15 KiB
C++
502 lines
15 KiB
C++
// Cyphesis Online RPG Server and AI Engine
|
|
// Copyright (C) 2000-2005 Alistair Riddoch
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// 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, write to the Free Software Foundation,
|
|
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
|
#include "MemMap.h"
|
|
|
|
#include "MemEntity.h"
|
|
#include "BaseMind.h"
|
|
#include "../Script.h"
|
|
#include "TypeResolver.h"
|
|
|
|
#include "common/id.h"
|
|
#include "common/debug.h"
|
|
#include "common/TypeNode.h"
|
|
#include "common/Inheritance.h"
|
|
|
|
#include <Atlas/Objects/Operation.h>
|
|
#include <Atlas/Objects/Anonymous.h>
|
|
|
|
static const bool debug_flag = false;
|
|
|
|
using Atlas::Message::Element;
|
|
using Atlas::Message::MapType;
|
|
using Atlas::Objects::Operation::Look;
|
|
using Atlas::Objects::Entity::RootEntity;
|
|
using Atlas::Objects::Entity::Anonymous;
|
|
|
|
using String::compose;
|
|
|
|
|
|
void MemMap::setScript(Script* script)
|
|
{
|
|
m_script = script;
|
|
}
|
|
|
|
|
|
Ref<MemEntity> MemMap::addEntity(const Ref<MemEntity>& entity)
|
|
{
|
|
assert(entity != nullptr);
|
|
assert(!entity->getId().empty());
|
|
|
|
debug_print("MemMap::addEntity " << entity->describeEntity() << " " << entity->getId());
|
|
long next = -1;
|
|
if (m_checkIterator != m_entities.end()) {
|
|
next = m_checkIterator->first;
|
|
}
|
|
m_entities[entity->getIntId()] = entity;
|
|
m_checkIterator = m_entities.find(next);
|
|
|
|
//Only signal if the type has been resolved, otherwise this will happen later when the entity obtains a type
|
|
if (entity->getType()) {
|
|
if (m_script) {
|
|
debug_print(this);
|
|
for (auto& hook : m_addHooks) {
|
|
m_script->hook(hook, entity.get());
|
|
}
|
|
}
|
|
|
|
if (m_listener) {
|
|
m_listener->entityAdded(*entity);
|
|
}
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
void MemMap::readEntity(const Ref<MemEntity>& entity, const RootEntity& ent, double timestamp)
|
|
// Read the contents of an Atlas message into an entity
|
|
{
|
|
if (ent->hasAttrFlag(Atlas::Objects::PARENT_FLAG)) {
|
|
auto& parent = ent->getParent();
|
|
auto type = m_typeResolver.requestType(parent, m_typeResolverOps);
|
|
|
|
if (type) {
|
|
entity->setType(type);
|
|
} else {
|
|
m_unresolvedEntities[parent].insert(entity);
|
|
}
|
|
}
|
|
entity->merge(ent->asMessage());
|
|
if (ent->hasAttrFlag(Atlas::Objects::Entity::LOC_FLAG)) {
|
|
auto old_loc = entity->m_location.m_parent;
|
|
const std::string& new_loc_id = ent->getLoc();
|
|
// Has LOC been changed?
|
|
if (!old_loc || new_loc_id != old_loc->getId()) {
|
|
entity->m_location.m_parent = getAdd(new_loc_id);
|
|
assert(entity->m_location.m_parent);
|
|
assert(old_loc != entity->m_location.m_parent);
|
|
if (old_loc) {
|
|
assert(old_loc->m_contains != nullptr);
|
|
old_loc->m_contains->erase(entity);
|
|
}
|
|
if (entity->m_location.m_parent->m_contains == nullptr) {
|
|
entity->m_location.m_parent->m_contains = new LocatedEntitySet{};
|
|
}
|
|
entity->m_location.m_parent->m_contains->insert(entity);
|
|
}
|
|
entity->m_location.readFromEntity(ent);
|
|
entity->m_location.update(timestamp);
|
|
}
|
|
addContents(ent);
|
|
}
|
|
|
|
void MemMap::updateEntity(const Ref<MemEntity>& entity, const RootEntity& ent, double timestamp)
|
|
// Update contents of entity an Atlas message.
|
|
{
|
|
assert(entity != nullptr);
|
|
|
|
debug_print(" got " << entity->describeEntity());
|
|
|
|
auto old_loc = entity->m_location.m_parent;
|
|
readEntity(entity, ent, timestamp);
|
|
|
|
//Only signal for those entities that have resolved types.
|
|
if (entity->getType()) {
|
|
if (m_script) {
|
|
auto K = m_updateHooks.begin();
|
|
auto Kend = m_updateHooks.end();
|
|
for (; K != Kend; ++K) {
|
|
m_script->hook(*K, entity.get());
|
|
}
|
|
}
|
|
if (m_listener) {
|
|
m_listener->entityUpdated(*entity, ent, old_loc.get());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
Ref<MemEntity> MemMap::newEntity(const std::string& id, long int_id,
|
|
const RootEntity& ent, double timestamp)
|
|
// Create a new entity from an Atlas message.
|
|
{
|
|
assert(m_entities.find(int_id) == m_entities.end());
|
|
|
|
Ref<MemEntity> entity = new MemEntity(id, int_id);
|
|
|
|
readEntity(entity, ent, timestamp);
|
|
|
|
return addEntity(entity);
|
|
}
|
|
|
|
MemMap::MemMap(TypeResolver& typeResolver)
|
|
: m_checkIterator(m_entities.begin()),
|
|
m_listener(nullptr),
|
|
m_typeResolver(typeResolver)
|
|
{
|
|
|
|
}
|
|
|
|
void MemMap::sendLooks(OpVector& res)
|
|
{
|
|
debug(std::cout << "MemMap::sendLooks" << std::endl << std::flush;);
|
|
auto I = m_additionsById.begin();
|
|
auto Iend = m_additionsById.end();
|
|
for (; I != Iend; ++I) {
|
|
Look l;
|
|
Anonymous look_arg;
|
|
look_arg->setId(*I);
|
|
l->setArgs1(look_arg);
|
|
res.push_back(l);
|
|
}
|
|
m_additionsById.clear();
|
|
}
|
|
|
|
Ref<MemEntity> MemMap::addId(const std::string& id, long int_id)
|
|
// Queue the ID of an entity we are interested in
|
|
{
|
|
assert(!id.empty());
|
|
assert(m_entities.find(int_id) == m_entities.end());
|
|
|
|
debug(std::cout << "MemMap::add_id" << std::endl << std::flush;);
|
|
m_additionsById.push_back(id);
|
|
Ref<MemEntity> entity = new MemEntity(id, int_id);
|
|
return addEntity(entity);
|
|
}
|
|
|
|
void MemMap::del(const std::string& id)
|
|
// Delete an entity from memory
|
|
{
|
|
debug(std::cout << "MemMap::del(" << id << ")" << std::endl << std::flush;);
|
|
|
|
long int_id = integerId(id);
|
|
|
|
auto I = m_entities.find(int_id);
|
|
if (I != m_entities.end()) {
|
|
auto ent = I->second;
|
|
assert(ent);
|
|
|
|
long next = -1;
|
|
if (m_checkIterator != m_entities.end()) {
|
|
next = m_checkIterator->first;
|
|
}
|
|
m_entities.erase(I);
|
|
|
|
|
|
if (next != -1) {
|
|
m_checkIterator = m_entities.find(next);
|
|
} else {
|
|
m_checkIterator = m_entities.begin();
|
|
}
|
|
|
|
//Only signal for those entities that have resolved types.
|
|
if (ent->getType()) {
|
|
if (m_script) {
|
|
for (auto& hook : m_deleteHooks) {
|
|
m_script->hook(hook, ent.get());
|
|
}
|
|
}
|
|
|
|
if (m_listener) {
|
|
m_listener->entityDeleted(*ent);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_unresolvedEntities.erase(id);
|
|
}
|
|
|
|
Ref<MemEntity> MemMap::get(const std::string& id) const
|
|
// Get an entity from memory
|
|
{
|
|
debug(std::cout << "MemMap::get" << std::endl << std::flush;);
|
|
if (id.empty()) {
|
|
// This shouldn't really occur, and shouldn't be a problem
|
|
log(ERROR, "MemMap::get queried for empty ID string.");
|
|
return nullptr;
|
|
}
|
|
|
|
long int_id = integerId(id);
|
|
|
|
auto I = m_entities.find(int_id);
|
|
if (I != m_entities.end()) {
|
|
assert(I->second != nullptr);
|
|
return I->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Ref<MemEntity> MemMap::getAdd(const std::string& id)
|
|
// Get an entity from memory, or add it if we haven't seen it yet
|
|
// This could be implemented by calling get() for all but the the last line
|
|
{
|
|
debug(std::cout << "MemMap::getAdd(" << id << ")" << std::endl << std::flush;);
|
|
if (id.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
long int_id = integerId(id);
|
|
|
|
if (int_id == -1) {
|
|
log(ERROR, String::compose("MemMap::getAdd: Invalid ID \"%1\".", id));
|
|
return nullptr;
|
|
}
|
|
|
|
auto I = m_entities.find(int_id);
|
|
if (I != m_entities.end()) {
|
|
assert(I->second != nullptr);
|
|
return I->second;
|
|
}
|
|
return addId(id, int_id);
|
|
}
|
|
|
|
void MemMap::addContents(const RootEntity& ent)
|
|
// Iterate over the contains attribute of a message, looking at all the contents
|
|
{
|
|
if (!ent->hasAttrFlag(Atlas::Objects::Entity::CONTAINS_FLAG)) {
|
|
return;
|
|
}
|
|
const std::list<std::string>& contlist = ent->getContains();
|
|
auto Jend = contlist.end();
|
|
auto J = contlist.begin();
|
|
for (; J != Jend; ++J) {
|
|
getAdd(*J);
|
|
}
|
|
}
|
|
|
|
Ref<MemEntity> MemMap::updateAdd(const RootEntity& ent, const double& d)
|
|
// Update an entity in our memory, from an Atlas message
|
|
// The mind code relies on this function never sending a Sight to
|
|
// be sure that seeing something created does not imply that the created
|
|
// entity is visible, as we may have received it because we can see the
|
|
// creator.
|
|
{
|
|
debug(std::cout << "MemMap::updateAdd" << std::endl << std::flush;);
|
|
if (!ent->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
|
|
log(ERROR, "MemMap::updateAdd, Missing id in updated entity");
|
|
return nullptr;
|
|
}
|
|
const std::string& id = ent->getId();
|
|
if (id.empty()) {
|
|
log(ERROR, "MemMap::updateAdd, Empty ID in updated entity.");
|
|
return nullptr;
|
|
}
|
|
|
|
long int_id = integerId(id);
|
|
|
|
if (int_id == -1) {
|
|
log(ERROR, String::compose("MemMap::updateAdd: Invalid ID \"%1\".", id));
|
|
return nullptr;
|
|
}
|
|
|
|
auto I = m_entities.find(int_id);
|
|
Ref<MemEntity> entity;
|
|
if (I == m_entities.end()) {
|
|
entity = newEntity(id, int_id, ent, d);
|
|
} else {
|
|
entity = I->second;
|
|
updateEntity(entity, ent, d);
|
|
}
|
|
entity->update(d);
|
|
return entity;
|
|
}
|
|
|
|
void MemMap::addEntityMemory(const std::string& id,
|
|
const std::string& memory,
|
|
const Element& value)
|
|
{
|
|
auto entity_memory = m_entityRelatedMemory.find(id);
|
|
if (entity_memory != m_entityRelatedMemory.end()) {
|
|
entity_memory->second[memory] = value;
|
|
} else {
|
|
m_entityRelatedMemory.insert(
|
|
std::make_pair(id, std::map<std::string, Element>{{memory, value}}));
|
|
}
|
|
}
|
|
|
|
void MemMap::recallEntityMemory(const std::string& id,
|
|
const std::string& memory,
|
|
Element& value) const
|
|
{
|
|
auto entity_memory = m_entityRelatedMemory.find(id);
|
|
if (entity_memory != m_entityRelatedMemory.end()) {
|
|
auto specific_memory = entity_memory->second.find(memory);
|
|
if (specific_memory != entity_memory->second.end()) {
|
|
value = specific_memory->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::map<std::string, std::map<std::string, Element>>& MemMap::getEntityRelatedMemory() const
|
|
{
|
|
return m_entityRelatedMemory;
|
|
}
|
|
|
|
|
|
EntityVector MemMap::findByType(const std::string& what)
|
|
// Find an entity in our memory of a certain type
|
|
{
|
|
EntityVector res;
|
|
|
|
auto Iend = m_entities.end();
|
|
for (auto I = m_entities.begin(); I != Iend; ++I) {
|
|
auto item = I->second;
|
|
debug(std::cout << "F" << what << ":" << item->getType() << ":" << item->getId() << std::endl << std::flush;);
|
|
if (item->isVisible() && item->getType()->name() == what) {
|
|
res.push_back(I->second.get());
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
EntityVector MemMap::findByLocation(const EntityLocation& loc,
|
|
WFMath::CoordType radius,
|
|
const std::string& what)
|
|
{
|
|
EntityVector res;
|
|
auto place = loc.m_parent;
|
|
if (place->m_contains == nullptr) {
|
|
return res;
|
|
}
|
|
#ifndef NDEBUG
|
|
auto place_by_id = get(place->getId());
|
|
if (place != place_by_id) {
|
|
log(ERROR, compose("MemMap consistency check failure: find location "
|
|
"has LOC %1(%2) which is different in dict (%3)",
|
|
place->getId(), place->getType()->name(),
|
|
place_by_id->getType()->name()));
|
|
return res;
|
|
}
|
|
#endif // NDEBUG
|
|
auto I = place->m_contains->begin();
|
|
auto Iend = place->m_contains->end();
|
|
float square_range = radius * radius;
|
|
for (; I != Iend; ++I) {
|
|
assert(*I != nullptr);
|
|
auto item = *I;
|
|
if (!item) {
|
|
log(ERROR, "Weird entity in memory");
|
|
continue;
|
|
}
|
|
if (!item->isVisible()) {
|
|
continue;
|
|
}
|
|
if (item->getType()->name() != what) {
|
|
continue;
|
|
}
|
|
if (squareDistance(loc.pos(), item->m_location.pos()) < square_range) {
|
|
res.push_back(item.get());
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void MemMap::check(const double& time)
|
|
{
|
|
|
|
if (m_checkIterator == m_entities.end()) {
|
|
m_checkIterator = m_entities.begin();
|
|
} else {
|
|
auto me = m_checkIterator->second;
|
|
assert(me);
|
|
if (me->getType() && !me->isVisible() && (time - me->lastSeen()) > 600 &&
|
|
(me->m_contains == nullptr || me->m_contains->empty())) {
|
|
m_checkIterator = m_entities.erase(m_checkIterator);
|
|
|
|
if (me->m_location.m_parent) {
|
|
me->m_location.m_parent->removeChild(*me);
|
|
me->m_location.m_parent = nullptr;
|
|
}
|
|
|
|
} else {
|
|
debug(std::cout << me->getId() << "|" << me->getType()->name() << "|"
|
|
<< me->lastSeen() << "|" << me->isVisible()
|
|
<< " is fine" << std::endl << std::flush;);
|
|
++m_checkIterator;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemMap::flush()
|
|
{
|
|
debug(std::cout << "Flushing memory with " << m_entities.size()
|
|
<< " memories" << std::endl << std::flush;);
|
|
m_entities.clear();
|
|
// MemEntityDict::const_iterator Iend = m_entities.end();
|
|
// for (MemEntityDict::const_iterator I = m_entities.begin(); I != Iend; ++I) {
|
|
// // FIXME This is required until MemMap uses parent refcounting
|
|
// I->second->m_location.m_parent = 0;
|
|
// I->second->decRef();
|
|
// }
|
|
}
|
|
|
|
void MemMap::setListener(MapListener* listener)
|
|
{
|
|
m_listener = listener;
|
|
}
|
|
|
|
std::vector<Ref<MemEntity>> MemMap::resolveEntitiesForType(const TypeNode* typeNode)
|
|
{
|
|
std::vector<Ref<MemEntity>> resolvedEntities;
|
|
|
|
auto I = m_unresolvedEntities.find(typeNode->name());
|
|
if (I != m_unresolvedEntities.end()) {
|
|
for (auto& entity : I->second) {
|
|
log(INFO, String::compose("Resolved entity %1.", entity->getId()));
|
|
entity->setType(typeNode);
|
|
|
|
if (m_script) {
|
|
debug_print(this);
|
|
for (auto& hook : m_addHooks) {
|
|
m_script->hook(hook, entity.get());
|
|
}
|
|
}
|
|
|
|
if (m_listener) {
|
|
m_listener->entityAdded(*entity);
|
|
}
|
|
|
|
resolvedEntities.emplace_back(entity);
|
|
|
|
}
|
|
m_unresolvedEntities.erase(I);
|
|
}
|
|
return resolvedEntities;
|
|
}
|
|
|
|
void MemMap::collectTypeResolverOps(OpVector& res)
|
|
{
|
|
res.insert(std::end(res), std::begin(m_typeResolverOps), std::end(m_typeResolverOps));
|
|
m_typeResolverOps.clear();
|
|
}
|
|
|
|
const TypeStore& MemMap::getTypeStore() const
|
|
{
|
|
return m_typeResolver.getTypeStore();
|
|
}
|
|
|