/* core/entity.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2. */ #include #include #include #include #include "auxiliary/functions.h" #include "sys/sys.h" #include "core/entity.h" #include "core/cvar.h" #include "core/application.h" #include "core/gameinterface.h" #include "core/gameserver.h" #include "BulletCollision/CollisionShapes/btBoxShape.h" namespace core { // maximal number of entities const size_t MAX_ENTITY = 1048574; // minimal speed const float SPEED_THRESHOLD = 0.001f; using math::Color; using math::Vector3f; /* ---- Static functions for the Entity registry ------------------- */ const InfoType *Entity::entity_infotype = 0; Entity::Registry Entity::entity_registry; size_t Entity::entity_nextid = 0; void Entity::add(Entity *ent) { Registry::iterator it; entity_nextid = (entity_nextid % MAX_ENTITY) + 1; // lowest entity-id is 1 unsigned int id = entity_nextid; for (it = entity_registry.begin(); it != entity_registry.end() && id == (*it).second->id(); it++) { id++; } ent->entity_id = id; entity_registry[id] = ent; } void Entity::add(Entity *ent, unsigned int id) { if (find(id)) { con_warn << "Duplicate entity " << id << "!\n"; return; } ent->entity_id = id; entity_registry[id] = ent; } Entity *Entity::find(unsigned int id) { Registry::iterator it = entity_registry.find(id); if (it == entity_registry.end()) { return 0; } else { return (*it).second; } } Entity *Entity::find(Entity * entity) { for (Registry::iterator it = entity_registry.begin(); it != entity_registry.end(); ++it) { if ((*it).second == entity) { return entity; } } return 0; } void Entity::erase(unsigned int id) { Registry::iterator it = entity_registry.find(id); if (it != entity_registry.end()) { delete((*it).second); (*it).second = 0; entity_registry.erase(it); } else { con_warn << "Could not erase entity " << id << "!\n"; } } void Entity::clear() { for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); it++) { delete(*it).second; } Entity::registry().clear(); entity_nextid = 0; } void Entity::list_header() { con_print << " " << " id " << "type " << "label " << "name" << std::endl; } void Entity::list(const Entity *entity) { con_print << " " << "^B" << std::setw(4) << entity->id() << " " << "^N" << std::setfill('0') << std::setw(4) << entity->type() << ":" << std::setw(4) << entity->moduletype() << std::setfill(' ') << " " << "^N" << aux::pad_right(entity->label(), 24) << " " << "^N" << entity->name() << std::endl; } void Entity::list() { list_header(); for (Registry::iterator it = entity_registry.begin(); it != entity_registry.end(); it++) { std::string typeindicator; Entity *entity = (*it).second; list(entity); } con_print << "^B " << entity_registry.size() << " entities" << std::endl; } /* ---- class Entity ----------------------------------------------- */ Entity::Entity() : entity_location(0.0f, 0.0f, 0.0f), entity_color(1.0f, 1.0f, 1.0f, 1.0f), entity_color_second(1.0f, 1.0f, 1.0f, 1.0f) { entity_id = 0; entity_flags = 0; entity_moduletypeid = 0; entity_model = 0; entity_body = 0; entity_body_info = 0; entity_collision_shape = 0; entity_speed = 0.0f; entity_radius = 0.5f; entity_shape = Diamond; entity_created = true; entity_died = false; entity_dirty = false; entity_keepalive = 0; entity_level = 1; entity_zone = 0; entity_oldzone = 0; entity_visible = true; entity_serverside = false; entity_inventory = 0; entity_slots = 0; entity_info = 0; entity_faction = 0; memset(entity_extension, 0, sizeof(entity_extension)); add(this); } Entity::Entity(std::istream & is) { entity_serverside = false; entity_id = 0; entity_zone = 0; entity_oldzone = 0; entity_visible = true; entity_keepalive = 0; entity_level = 1; entity_model = 0; entity_body = 0; entity_body_info = 0; entity_collision_shape = 0; entity_speed = 0.0f; entity_created = true; entity_died = false; entity_inventory = 0; entity_slots = 0; entity_info = 0; entity_faction = 0; memset(entity_extension, 0, sizeof(entity_extension)); } Entity::~Entity() { // delete extensions for (size_t i = 0; i < 4; i++) { if (entity_extension[i]) delete entity_extension[i]; entity_extension[i] = 0; } // delete entity menus for (Menus::iterator mit = menus().begin(); mit != menus().end(); mit++) { delete (*mit); (*mit) = 0; } menus().clear(); // delete equipment slots if (entity_slots) { delete entity_slots; } // delete inventory if (entity_inventory) { delete entity_inventory; } // remove entity from zone if (entity_zone) { entity_zone->remove(this); if (body() && entity_zone->physics()) { entity_zone->physics()->removeRigidBody(body()); } } // delete collision shape if (entity_collision_shape) { delete entity_collision_shape; entity_collision_shape = 0; } // delete child collision shapes for (CollisionShapes::iterator sit = entity_collision_child_shapes.begin(); sit != entity_collision_child_shapes.end(); sit++) { delete (*sit); (*sit) = 0; } entity_collision_child_shapes.clear(); // delete collision body if (entity_body) { delete entity_body; } // delete collision body construction information if (entity_body_info) { delete entity_body_info; } } void Entity::print() const { // print header print_header(); // entity type id con_print << " ^Ntype ^B"; switch (type()) { case Default: con_print << "^Bentity"; break; case Dynamic: con_print << "^Bdynamic"; break; case Controlable: con_print << "^Bcontrolable"; break; case Globe: con_print << "^Bglobe"; break; case Projectile: con_print << "^Bprojectile"; } con_print << std::endl; // game module type id con_print << " ^Nmodule type ^B" << moduletype() << std::endl; // print entity flags con_print << " ^Nflags ^B"; if (flags() == 0) { if (has_flag(NonSolid)) { con_print << " ^Bnonsolid"; } if (has_flag(Bright)) { con_print << " ^Bbright"; } if (has_flag(Dockable)) { con_print << " ^Bdockable"; } if (has_flag(ShowOnMap)) { con_print << " ^Bshownmap"; } if (has_flag(KeepAlive)) { con_print << " ^Bkeepalive"; } } else { con_print << " ^Bnone"; } con_print << std::endl; // level con_print << " ^Nlevel ^B" << level() << std::endl; // faction if (faction()) { con_print << " ^Nfaction ^Nid ^B" << faction()->id() << " ^Nlabel ^B" << faction()->label() << std::endl; } // info record if (info()) { con_print << " ^Ninfo ^Nid ^B" << info()->id() << " ^Nlabel ^B" << info()->label() << std::endl; } // model or shape if (model()) { con_print << " ^Nmodel ^B" << model()->name() << std::endl; } else { con_print << " ^Nshape ^B"; switch (shape()) { case Diamond: con_print << "diamond"; break; case Sphere: con_print << "sphere"; break; case Cube: con_print << "cube"; break; case Axis: con_print << "axis"; break; } } // slots if (slots() && slots()->size()) { int nbcannons = 0; int nbturrets = 0; int nbdocks = 0; for (Slots::iterator slit = slots()->begin(); slit != slots()->end(); ++slit) { switch ((*slit)->type()) { case model::Slot::Cannon: ++nbcannons; break; case model::Slot::Turret: ++nbturrets; break; case model::Slot::Dock: ++nbdocks; break; case model::Slot::None: break; } } con_print << " ^Nslots "; if (nbcannons > 0) { con_print << " ^B" << nbcannons << " ^Ncannons"; } if (nbturrets > 0) { con_print << " ^B" << nbturrets << " ^Nturrets"; } if (nbdocks > 0) { con_print << " ^B" << nbdocks << " ^Ndocks"; } con_print << std::endl; } // invenotry if (inventory()) { print_inventory(); } } void Entity::print_header() const { con_print << " entity id ^B" << id() << " ^Nlabel ^B" << label() << " ^Nname ^B" << name(); if (zone()) { con_print << " ^Nzone ^B" << zone()->label(); } con_print << std::endl; } void Entity::print_inventory() const { if (!entity_inventory) { con_print << "no inventory availble" << std::endl; return; } con_print << " ^Ninventory capacity ^B" << inventory()->capacity_used() << "/" << inventory()->capacity() << " ^Nused by^B " << inventory()->items().size() << " ^Nitems" << std::endl; con_print << " ^Nitem infotype info amount price flags" << std::endl; for (Inventory::Items::const_iterator it = entity_inventory->items().begin(); it != entity_inventory->items().end(); it++) { Item *item = (*it); // TODO item flags con_print << " " << " ^B" << std::setw(4) << item->id() << " ^N" << aux::pad_right((item->info()->type() ? item->info()->type()->label() : "NULL"), 8) << " ^N" << aux::pad_right(item->info()->label(), 24) << std::setw(5) << item->amount() << " " << std::setw(9) << item->price(); if (item->has_flag(core::Item::Tradeable)) { con_print << " tradeable"; } if (item->has_flag(core::Item::Unique)) { con_print << " unique"; } if (item->has_flag(core::Item::Unrestricted)) { con_print << " unrestricted"; } if (item->has_flag(core::Item::Mounted)) { con_print << " mounted"; } con_print<< std::endl; } } void Entity::die() { entity_died = true; } void Entity::clear_updates() { entity_created = false; entity_dirty = false; entity_oldzone = 0; if (entity_inventory && entity_inventory->dirty()) { // inventory timestamp must be set for singleplayer entity_inventory->set_timestamp(game()->timestamp()); entity_inventory->set_dirty(false); } } void Entity::set_info(const Info *info) { entity_info = info; } Inventory *Entity::add_inventory() { if (!entity_inventory) { entity_inventory = new Inventory(); } return(entity_inventory); } Slots *Entity::add_slots() { if (!entity_slots) { entity_slots = new Slots(); } return entity_slots; } void Entity::set_zone(Zone *zone) { if (entity_zone == zone) return; if (entity_zone) { entity_zone->remove(this); if (body() && entity_zone->physics()) { entity_zone->physics()->removeRigidBody(body()); } } // oldzone is cleared after every game frame if (!entity_oldzone) entity_oldzone = entity_zone; entity_zone = zone; entity_dirty = true; if (entity_zone) { entity_zone->add(this); if (body() && entity_zone->physics()) { entity_zone->physics()->addRigidBody(body()); reset(); } } } void Entity::set_model(model::Model *model) { // server-side property should not clear modelname entity_model = model; if (entity_model) { entity_modelname.assign(entity_model->name()); } } void Entity::set_modelname(const std::string &modelname) { if (!modelname.size()) { set_model(0); } else { set_model(model::Model::load(modelname)); } if (!entity_model) entity_modelname.clear(); } void Entity::serialize_server_create(std::ostream & os) const { os << moduletype() << " " << flags() << " " << (visible() ? 1 : 0) << " " << (zone() ? zone()->id() : 0) << " " << std::setprecision(8) << entity_location << " " << color() << " " << color_second() << " " << shape() << " " << radius() << " " << std::setprecision(8) << entity_axis.forward() << " " << std::setprecision(8) << entity_axis.left() << " " << "\"" <name() : "") << "\" " << (faction() ? faction()->id() : 0) << " " << (info() ? info()->id() : 0) << " " << (inventory() ? 1 : 0) << " "; if (inventory()) inventory()->serialize_server_update(os); } void Entity::receive_server_create(std::istream &is) { unsigned int s; unsigned int zo; unsigned int o = 0; std::string n; char c; is >> entity_moduletypeid; is >> entity_flags; is >> o; if (o) entity_visible = true; else entity_visible = false; is >> zo; set_zone(Zone::find(zo)); if (entity_zone && !zo) { con_warn << "Received entity " << entity_id << " for unknown zone " << zo << "!" << std::endl; } is >> entity_location; is >> entity_color; is >> entity_color_second; is >> s; // shape entity_shape = (Shape) s; is >> entity_radius; is >> entity_axis[0]; is >> entity_axis[1]; entity_axis[2] = math::crossproduct(entity_axis.forward(), entity_axis.left()); // read label n.clear(); while ((is.get(c)) && (c != '"')); while ((is.get(c)) && (c != '"')) n += c; set_label(n); // read name n.clear(); while ((is.get(c)) && (c != '"')); while ((is.get(c)) && (c != '"')) n += c; set_name(n); // read model name n.clear(); while ((is.get(c)) && (c != '"')); while ((is.get(c)) && (c != '"')) n += c; set_modelname(n); // read faction id if(is >> o) { entity_faction = Info::find(o); if (o && !entity_faction) entity_faction = new Info(o); } else { entity_faction = 0; } // read info id if(is >> o) { entity_info = Info::find(o); if (o && !entity_info) entity_info = new Info(o); } else { entity_info = 0; } // has inventory if (is >> o) { if (!o) { if (inventory()) { con_warn << "No inventory received for entity " << id() << " " << label() << " with inventory!" << std::endl; entity_inventory->clear(); } } else { if (!inventory()) { entity_inventory = new Inventory(); } } } else { if (inventory()) { con_warn << "No inventory received for entity " << id() << " " << label() << " with inventory!" << std::endl; entity_inventory->clear(); } } if (inventory()) { inventory()->receive_server_update(is); } entity_dirty = false; } void Entity::serialize_client_update(std::ostream & os) const { } void Entity::receive_client_update(std::istream &is) { } void Entity::serialize_server_update(std::ostream & os) const { } void Entity::receive_server_update(std::istream &is) { } void Entity::upkeep(const unsigned long timestamp) { } void Entity::add_menu(MenuDescription *menu) { entity_menus.push_back(menu); } MenuDescription *Entity::find_menu(std::string const &label) { for (Menus::iterator it = menus().begin(); it != menus().end(); it++) { if (label.compare((*it)->label()) == 0) return (*it); } return 0; } void Entity::remove_menu(std::string const &label) { for (Menus::iterator it = menus().begin(); it != menus().end(); it++) { if (label.compare((*it)->label()) == 0) { menus().erase(it); return; } } } void Entity::frame(const unsigned long elapsed) { if (entity_collision_child_shapes.size() > 0) { btCompoundShape *compoundshape = static_cast (entity_collision_shape); const float modelscale = radius() / model()->radius(); for (int n = 0; n < compoundshape->getNumChildShapes(); n++) { model::CollisionMesh *mesh = static_cast(compoundshape->getChildShape(n)->getUserPointer()); if (mesh) { if (mesh->type() == model::FragmentGroup::Rotate) { const float rotation_radians = math::degrees360f(game()->time() * mesh->speed()) * M_PI / 180.0f; math::Axis rotation_axis; rotation_axis.rotate(mesh->movement(), rotation_radians); btTransform child_transform; child_transform.setIdentity(); child_transform.setOrigin(math::to_btVector3(mesh->location() * modelscale )); child_transform.setBasis(math::to_btMatrix3x3(mesh->axis() * rotation_axis)); compoundshape->updateChildTransform(n, child_transform); } else if (mesh->type() == model::FragmentGroup::Move ) { const float freq = mesh->speed() / mesh->distance(); math::Vector3f translation(mesh->movement() * mesh->distance()); translation *= sinf(core::game()->time() * M_PI * freq) * 0.5f + 0.5f; btTransform child_transform; child_transform.setIdentity(); child_transform.setOrigin(to_btVector3((mesh->location() + mesh->axis() * translation * mesh->scale()) * modelscale)); child_transform.setBasis(math::to_btMatrix3x3(mesh->axis())); compoundshape->updateChildTransform(n, child_transform); } } } } } void Entity::reset() { if (!radius() || has_flag(NonSolid)) { return; } // location and orientation btTransform t; t.setIdentity(); t.setOrigin(to_btVector3(location())); t.setBasis(to_btMatrix3x3(axis())); // construct physics body if required if (!entity_body) { // if there is no body, this entity should not have shapes either assert (entity_collision_shape == 0); assert (entity_collision_child_shapes.size() == 0); // create collision shape if (model() && model()->radius()) { const float modelscale = radius() / model()->radius(); if (model()->collisionmodel() && model()->collisionmodel()->size()) { // a valid collision model has been found, use it to construct a compoundshape btCompoundShape *compoundshape = new btCompoundShape(); for (model::CollisionModel::CollisionMeshes::iterator it = model()->collisionmodel()->meshes().begin(); it != model()->collisionmodel()->meshes().end(); it++) { model::CollisionMesh *mesh = (*it); // generate a btCollisionShape for each model::CollisionMesh in the model::CollisionModel // static entities use a btBvhTriangleMeshShape btBvhTriangleMeshShape *meshshape = new btBvhTriangleMeshShape(mesh->triangles(), true, true); meshshape->setMargin(Cvar::sv_collisionmargin->value()); const float meshscale = mesh->scale() * modelscale; const btVector3 meshscalevec(meshscale, meshscale, meshscale); // TODO // This does not work as advertized, it is actually the underlying mesh that // gets scaled. If there are multiple entities with the same model at a different scale, // the scale is set per-model instead of per-entity // Note that it is still possible to reuse and rescale the same model used by an Entitycontrolable // (and most likely, EntityDynamic as well) meshshape->setLocalScaling(meshscalevec); meshshape->buildOptimizedBvh(); meshshape->recalcLocalAabb(); // apply collision mesh properties to the btCollisionShape btTransform child_transform; child_transform.setIdentity(); child_transform.setOrigin(to_btVector3(mesh->location() * modelscale)); child_transform.setBasis(to_btMatrix3x3(mesh->axis())); switch (mesh->type()) { case model::FragmentGroup::Rotate: case model::FragmentGroup::Move: meshshape->setUserPointer((void *) mesh); break; default: meshshape->setUserPointer(0); break; } // add the mesh to the compoundhape and store a pointer to the child shape compoundshape->addChildShape(child_transform, meshshape); entity_collision_child_shapes.push_back(meshshape); } entity_collision_shape = compoundshape; } else { // model without collision model // use bounding box btBoxShape *boxshape = new btBoxShape(to_btVector3(model()->box().max() * modelscale)); entity_collision_shape = boxshape; } } else { // no model // use a sphere with a radius matching the entity btSphereShape *sphereshape = new btSphereShape(radius()); entity_collision_shape = sphereshape; // TODO the actual shape should depend on Entity::Shape } entity_collision_shape->setMargin(Cvar::sv_collisionmargin->value()); // create physics body btVector3 inertia(0, 0, 0); entity_body_info = new btRigidBody::btRigidBodyConstructionInfo(0, 0, entity_collision_shape, inertia); entity_body = new btRigidBody(*entity_body_info); //entity_body->setActivationState(ISLAND_SLEEPING); entity_body->setActivationState(WANTS_DEACTIVATION); entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT); entity_body->setUserPointer((void *) this); if (zone()) zone()->physics()->addRigidBody(entity_body); } // transfer entity location to body body()->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->clearForces(); body()->setWorldTransform(t); set_dirty(); } /* ---- class EntityDynamic ---------------------------------------- */ EntityDynamic::EntityDynamic() : Entity() { entity_state = Normal; entity_timer = 0; entity_motionstate = 0; entity_mass = 0.0f; } EntityDynamic::EntityDynamic(std::istream & is) : Entity(is) { entity_state = Normal; entity_timer = 0; entity_motionstate = 0; entity_mass = 0.0f; } EntityDynamic::~EntityDynamic() { if (entity_motionstate) delete entity_motionstate; } void EntityDynamic::print() const { Entity::print(); con_print << " ^Nstate ^B"; switch (state()) { case Normal: con_print << "normal"; break; case NoPower: con_print << "nopower"; break; case ImpulseInitiate: con_print << "impulseinitiate"; break; case Impulse: con_print << "impulse"; break; case JumpInitiate: con_print << "jumpinitiate"; break; case Jump: con_print << "jump"; break; case Docked: con_print << "docked"; break; case Destroyed: con_print << "destroyed"; break; } con_print << std::endl; } void EntityDynamic::set_state(int state) { if (this->state() != state) { entity_state = state; set_dirty(); } } void EntityDynamic::nudge(const bool randomize) { if (!zone()) { return; } const float DELTA = 0.001f; for (Zone::Content::const_iterator it = zone()->content().begin(); it != zone()->content().end(); ) { const Entity *other = (*it); if ((other->type() != Projectile) && (other != this) && !other->serverside() && !other->has_flag(Entity::NonSolid) ) { // check distance const float d = math::distance(location(), other->location()); const float r = radius() + other->radius(); if ( d < r ) { if (!randomize) { // nudge forward set_location(location() + axis().forward() * r); } else if (d < DELTA) { // nudge into randomize direction math::Axis nudge_axis(axis()); nudge_axis.change_direction(math::randomf(360.0f)); nudge_axis.change_pitch(math::randomf(180.0f) - 90.0f); set_location(location() + nudge_axis.forward() * r); } else { math::Vector3f direction(location() - other->location()); direction.normalize(); set_location(location() + direction * r); } // restart search it = zone()->content().begin(); } else { // next entity ++it; } } else { // next entity ++it; } } } void EntityDynamic::reset() { // no bullet physics on NonSolid entities if (!radius() || has_flag(NonSolid)) { return; } // remove Docked and Destroyed entities from the physics simulation if (died() || (state() == core::Entity::Docked) || (state() == core::Entity::Destroyed)) { if (entity_body) { if (entity_motionstate) { delete entity_motionstate; entity_motionstate = 0; } if (zone() && zone()->physics()) { entity_zone->physics()->removeRigidBody(body()); } if (entity_collision_shape) { delete entity_collision_shape; entity_collision_shape = 0; } for (CollisionShapes::iterator sit = entity_collision_child_shapes.begin(); sit != entity_collision_child_shapes.end(); sit++) { delete (*sit); (*sit) = 0; } entity_collision_child_shapes.clear(); if (entity_body) { delete entity_body; entity_body = 0; } if (entity_body_info) { delete entity_body_info; entity_body_info = 0; } } return; } // location and orientation btTransform t; t.setIdentity(); t.setOrigin(to_btVector3(location())); t.setBasis(to_btMatrix3x3(axis())); // construct physics body if necessary if (!entity_body) { // create collision shape if (model() && model()->radius()) { const float modelscale = radius() / model()->radius(); if (model()->collisionmodel() && model()->collisionmodel()->size()) { // complex collision is enabled and a valid collision model has been found btCompoundShape *compoundshape = new btCompoundShape(); for (model::CollisionModel::CollisionMeshes::iterator it = model()->collisionmodel()->meshes().begin(); it != model()->collisionmodel()->meshes().end(); it++) { model::CollisionMesh *mesh = (*it); // generate a btCollisionShape for each model::CollisionMesh in the model::CollisionModel // dynamic entities use btGImpactMeshShape btGImpactMeshShape *meshshape = new btGImpactMeshShape(mesh->triangles()); meshshape->setMargin(Cvar::sv_collisionmargin->value()); const float meshscale = mesh->scale() * modelscale; const btVector3 meshscalevec(meshscale, meshscale, meshscale); meshshape->setLocalScaling(meshscalevec); meshshape->updateBound(); // apply collision mesh properties to the btCollisionShape btTransform child_transform; child_transform.setIdentity(); child_transform.setOrigin(to_btVector3(mesh->location() * modelscale)); child_transform.setBasis(to_btMatrix3x3(mesh->axis())); switch (mesh->type()) { case model::FragmentGroup::Rotate: case model::FragmentGroup::Move: meshshape->setUserPointer((void *) mesh); break; default: meshshape->setUserPointer(0); } // add the mesh to the compoundhape and store a pointer to the child shape compoundshape->addChildShape(child_transform, meshshape); entity_collision_child_shapes.push_back(meshshape); } entity_collision_shape = compoundshape; } else { // use bounding box btBoxShape *boxshape = new btBoxShape(to_btVector3(model()->box().max() * modelscale)); entity_collision_shape = boxshape; } } else { // use a sphere with a radius matching the entity btSphereShape *sphereshape = new btSphereShape(radius()); entity_collision_shape = sphereshape; } // set margin entity_collision_shape->setMargin(Cvar::sv_collisionmargin->value()); // calculate inertia btVector3 inertia(0, 0, 0); if (entity_mass) entity_collision_shape->calculateLocalInertia(entity_mass, inertia); // create motion state entity_motionstate = new btDefaultMotionState(t); // create physics body entity_body_info = new btRigidBody::btRigidBodyConstructionInfo(entity_mass, entity_motionstate, entity_collision_shape, inertia); entity_body = new btRigidBody(*entity_body_info); // point the bullet user pointer to the entity entity_body->setUserPointer((void *) this); // enable custom collision callback entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); if (entity_mass) { entity_body->setActivationState(DISABLE_DEACTIVATION); } else { entity_body->setActivationState(ISLAND_SLEEPING); } if (zone()) zone()->physics()->addRigidBody(entity_body); } // transfer entity location to motion state body()->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->setWorldTransform(t); body()->clearForces(); if (motionstate()) { motionstate()->setWorldTransform(t); } /* Does not work * Note: does not do what you'd think it does if (entity_mass) { if (entity_state == Docked) { entity_body->setActivationState(DISABLE_SIMULATION); entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE); } else { entity_body->setCollisionFlags(entity_body->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE); entity_body->setActivationState(DISABLE_DEACTIVATION); } } */ set_dirty(); } void EntityDynamic::frame(const unsigned long elapsed) { Entity::frame(elapsed); if (entity_state == Docked) { return; } // transfer bullet state to entity state if (entity_body) { btTransform t; entity_motionstate->getWorldTransform(t); get_location().assign(t.getOrigin()); get_axis().assign(t.getBasis()); // if speed goes to 0 in this frame, the entity still needs to be marked as dirty if (entity_speed > 0) { set_dirty(); } if (entity_speed < SPEED_THRESHOLD) { entity_body->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); entity_speed = 0.0f; } else { entity_speed = (float) entity_body->getLinearVelocity().length(); } if (entity_speed > 0) { set_dirty(); } float entity_angular = (float) entity_body->getAngularVelocity().length(); if (entity_angular > 0) { set_dirty(); } if (entity_angular < SPEED_THRESHOLD) { entity_body->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); } } } void EntityDynamic::collision(Entity *other) { } void EntityDynamic::hit(Entity *other) { } void EntityDynamic::serialize_server_create(std::ostream & os) const { Entity::serialize_server_create(os); os << roundf(entity_speed * 100.0f) << " " << entity_state << " "; if (entity_state != Normal) { os << entity_timer << " "; } } void EntityDynamic::receive_server_create(std::istream &is) { Entity::receive_server_create(is); is >> entity_speed; entity_speed /= 100.0f; is >> entity_state; if (entity_state != Normal) { is >> entity_timer; } else { entity_timer = 0; } } void EntityDynamic::serialize_client_update(std::ostream & os) const { } void EntityDynamic::receive_client_update(std::istream &is) { } void EntityDynamic::serialize_server_update(std::ostream & os) const { os << (visible() ? 1 : 0) << " "; if (visible()) { os << std::setprecision(8) << location() << " " << axis().forward() << " " << axis().left() << " " << roundf(entity_speed * 100.0f) << " " << entity_state << " "; if (entity_state != Normal) { os << entity_timer << " "; } } } void EntityDynamic::receive_server_update(std::istream &is) { unsigned int o = 0; is >> o; // visibility if (o) { set_visible(); is >> get_location(); // axis up vector is the crossproduct of forward and left is >> get_axis()[0]; is >> get_axis()[1]; get_axis()[2] = math::crossproduct(axis().forward(), axis().left()); is >> entity_speed; entity_speed /= 100.0f; is >> entity_state; if (entity_state != Normal) { is >> entity_timer; } else { entity_timer = 0; } } else { set_visible(false); } } /*----- EntityControlable::ActionInterface ------------------------- */ EntityControlable::ActionInterface::ActionInterface(EntityControlable *entity) { actioninterface_entity = entity; } EntityControlable::ActionInterface::~ActionInterface() { actioninterface_entity = 0; } void EntityControlable::ActionInterface::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { actioninterface_entity->action(deltaTimeStep); } void EntityControlable::ActionInterface::debugDraw(btIDebugDraw* debugDrawer) { } /*----- EntityControlable ------------------------------------------ */ // server-side constructor EntityControlable::EntityControlable() : EntityDynamic() { entity_thrust = 0; target_direction = 0.0f; target_thrust = 0.0f; target_pitch = 0.0f; target_roll = 0.0f; target_strafe = 0.0f; target_afterburner = 0.0f; target_controlflags = 0; entity_owner = 0; entity_actioninterface = 0; entity_health = 100.0f; } // client-side constructor EntityControlable::EntityControlable(std::istream & is) : EntityDynamic(is) { entity_thrust = 0; target_direction = 0.0f; target_thrust = 0.0f; target_pitch = 0.0f; target_roll = 0.0f; target_strafe = 0.0f; target_afterburner = 0.0f; target_controlflags = 0; entity_owner = 0; entity_actioninterface = 0; entity_health = 100.0f; } EntityControlable::~EntityControlable() { if (entity_owner) entity_owner->remove_asset(this); if (entity_actioninterface) { entity_zone->physics()->removeAction(entity_actioninterface); delete entity_actioninterface; } } void EntityControlable::set_owner(Player *owner) { if (entity_owner) entity_owner->remove_asset(this); entity_owner = owner; if (entity_owner) entity_owner->add_asset(this); } void EntityControlable::set_thrust(float thrust) { if (entity_thrust != thrust) { entity_thrust = thrust; set_dirty(); } } void EntityControlable::serialize_server_create(std::ostream & os) const { EntityDynamic::serialize_server_create(os); os << roundf(entity_thrust*100.0f) << " "; os << (entity_owner ? entity_owner->id() : 0) << " "; os << roundf(health()) << " "; } void EntityControlable::receive_server_create(std::istream &is) { EntityDynamic::receive_server_create(is); is >> entity_thrust; entity_thrust /= 100.0f; entity_owner = 0; int owner_id = 0; is >> owner_id; if (owner_id && !owner()) { for (GameInterface::Players::iterator pit = game()->players().begin();!owner() && (pit != game()->players().end()); pit++) { Player *player = (*pit); if (player->id() == owner_id) { player->add_asset(this); // entity_owner = player; // add_asset already sets this } } if (!entity_owner) { con_warn << "could not find owner " << owner_id << " for entity " << id() << "\n"; } } is >> entity_health; } void EntityControlable::serialize_client_update(std::ostream & os) const { EntityDynamic::serialize_client_update(os); os << target_direction << " "; os << target_pitch << " "; os << target_thrust << " "; os << target_roll << " "; os << target_strafe << " "; os << target_vstrafe << " "; os << target_afterburner << " "; os << target_controlflags << " "; os << target_aim << " "; } void EntityControlable::receive_client_update(std::istream &is) { EntityDynamic::receive_client_update(is); is >> target_direction; is >> target_pitch; is >> target_thrust; is >> target_roll; is >> target_strafe; is >> target_vstrafe; is >> target_afterburner; is >> target_controlflags; is >> target_aim; } void EntityControlable::serialize_server_update(std::ostream & os) const { EntityDynamic::serialize_server_update(os); os << roundf(thrust() * 100.0f) << " "; os << roundf(health()) << " "; } void EntityControlable::receive_server_update(std::istream &is) { EntityDynamic::receive_server_update(is); is >> entity_thrust; entity_thrust /= 100.0f; is >> entity_health; } void EntityControlable::set_target_thrust(float thrust) { if (thrust != target_thrust) { target_thrust = thrust; set_dirty(); } } void EntityControlable::set_target_direction(float direction) { if (target_direction != direction) { target_direction = direction; set_dirty(); } } void EntityControlable::set_target_pitch(float pitch) { if (target_pitch != pitch) { target_pitch = pitch; set_dirty(); } } void EntityControlable::set_target_roll(float roll) { if (target_roll != roll) { target_roll = roll; set_dirty(); } } void EntityControlable::set_target_strafe(float strafe) { if (target_strafe != strafe) { target_strafe = strafe; set_dirty(); } } void EntityControlable::set_target_vstrafe(float vstrafe) { if (target_vstrafe != vstrafe) { target_vstrafe = vstrafe; set_dirty(); } } void EntityControlable::set_target_aim(const math::Vector3f &aim) { if (aim != target_aim) { target_aim.assign(aim); set_dirty(); } } void EntityControlable::set_target_afterburner(float afterburner) { if (target_afterburner != afterburner) { target_afterburner = afterburner; set_dirty(); } } void EntityControlable::set_target_controlflags(int controlflags) { if (target_controlflags != controlflags) { target_controlflags = controlflags; set_dirty(); } } void EntityControlable::set_target_controlflag(const ControlFlags controlflag) { if ((target_controlflags & controlflag) == 0) { target_controlflags = target_controlflags | controlflag; set_dirty(); } } void EntityControlable::unset_target_controlflag(const ControlFlags controlflag) { if ((target_controlflags & controlflag) == controlflag) { target_controlflags = target_controlflags & ~controlflag; set_dirty(); } } void EntityControlable::set_zone(Zone *zone) { if (entity_zone == zone) return; if (entity_zone) { entity_zone->remove(this); if (body() && entity_zone->physics()) { entity_zone->physics()->removeAction(entity_actioninterface); entity_zone->physics()->removeRigidBody(body()); } } // oldzone is cleared after every game frame if (!entity_oldzone) entity_oldzone = entity_zone; entity_zone = zone; set_dirty(); if (entity_zone) { entity_zone->add(this); if (body() && entity_zone->physics()) { entity_zone->physics()->addRigidBody(body()); entity_zone->physics()->addAction(entity_actioninterface); reset(); } } } void EntityControlable::reset() { // no bullet physics on NonSolid entities if (!radius() || has_flag(NonSolid)) { return; } // remove Docked and Destroyed entities from the physics simulation if (died() || (state() == core::Entity::Docked) || (state() == core::Entity::Destroyed)) { if (entity_body) { if (entity_motionstate) { delete entity_motionstate; entity_motionstate = 0; } if (zone() && zone()->physics()) { if (entity_actioninterface) { entity_zone->physics()->removeAction(entity_actioninterface); delete entity_actioninterface; entity_actioninterface = 0; } entity_zone->physics()->removeRigidBody(body()); } if (entity_collision_shape) { delete entity_collision_shape; entity_collision_shape = 0; } for (CollisionShapes::iterator sit = entity_collision_child_shapes.begin(); sit != entity_collision_child_shapes.end(); sit++) { delete (*sit); (*sit) = 0; } entity_collision_child_shapes.clear(); if (entity_body) { delete entity_body; entity_body = 0; } if (entity_body_info) { delete entity_body_info; entity_body_info = 0; } } return; } // location and orientation btTransform t; t.setIdentity(); t.setOrigin(to_btVector3(location())); t.setBasis(to_btMatrix3x3(axis())); // construct physics body if necessary if (!entity_body) { // create collision shape if (model() && model()->radius()) { const float modelscale = radius() / model()->radius(); if (model()->collisionmodel() && model()->collisionmodel()->size()) { // a valid collision model has been found btCompoundShape *compoundshape = new btCompoundShape(); for (model::CollisionModel::CollisionMeshes::iterator it = model()->collisionmodel()->meshes().begin(); it != model()->collisionmodel()->meshes().end(); it++) { model::CollisionMesh *mesh = (*it); // generate a btCollisionShape for each model::CollisionMesh in the model::CollisionModel // dynamic entities use btGImpactMeshShape btGImpactMeshShape *meshshape = new btGImpactMeshShape(mesh->triangles()); meshshape->setMargin(Cvar::sv_collisionmargin->value()); const float meshscale = mesh->scale() * modelscale; const btVector3 meshscalevec(meshscale, meshscale, meshscale); meshshape->setLocalScaling(meshscalevec); meshshape->updateBound(); // apply collision mesh properties to the btCollisionShape btTransform child_transform; child_transform.setIdentity(); child_transform.setOrigin(to_btVector3(mesh->location() * modelscale)); child_transform.setBasis(to_btMatrix3x3(mesh->axis())); switch (mesh->type()) { case model::FragmentGroup::Rotate: case model::FragmentGroup::Move: meshshape->setUserPointer((void *) mesh); break; default: meshshape->setUserPointer(0); } // add the mesh to the compoundhape and store a pointer to the child shape compoundshape->addChildShape(child_transform, meshshape); entity_collision_child_shapes.push_back(meshshape); } entity_collision_shape = compoundshape; } else { // use bounding box btBoxShape *boxshape = new btBoxShape(to_btVector3(model()->box().max() * modelscale)); entity_collision_shape = boxshape; } } else { // use a sphere with a radius matching the entity btSphereShape *sphereshape = new btSphereShape(radius()); entity_collision_shape = sphereshape; } // set margin on each mesh entity_collision_shape->setMargin(Cvar::sv_collisionmargin->value()); btVector3 inertia(0, 0, 0); if (entity_mass) entity_collision_shape->calculateLocalInertia(entity_mass, inertia); // create motion state entity_motionstate = new btDefaultMotionState(t); // create physics body entity_body_info = new btRigidBody::btRigidBodyConstructionInfo(entity_mass, entity_motionstate, entity_collision_shape, inertia); entity_body = new btRigidBody(*entity_body_info); entity_body->setActivationState(DISABLE_DEACTIVATION); // point the bullet user pointer to the entity entity_body->setUserPointer((void *) this); // enable custom collision callback entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); //entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); entity_actioninterface = new ActionInterface(this); if (zone()) { entity_zone->physics()->addRigidBody(body()); entity_zone->physics()->addAction(entity_actioninterface); } } body()->clearForces(); body()->setLinearVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->setAngularVelocity(btVector3(0.0f, 0.0f, 0.0f)); body()->setWorldTransform(t); motionstate()->setWorldTransform(t); /* Does not work if (entity_state == Docked) { entity_body->setActivationState(DISABLE_SIMULATION); entity_body->setCollisionFlags(entity_body->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE); } else { entity_body->setActivationState(DISABLE_DEACTIVATION); entity_body->setCollisionFlags(entity_body->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE); } */ set_dirty(); } // bullet physics frame (runs at bullet framerate) void EntityControlable::action(btScalar seconds) { } } //namespace core