#include "empire.h" #include "obj/object.h" #include "obj/universe.h" #include "obj/obj_group.h" #include "constants.h" #include "quaternion.h" #include "util/random.h" #include "main/references.h" #include "network/network_manager.h" #include "network.h" #include "obj/lock.h" #include "design/effect.h" #include "str_util.h" #include "compat/misc.h" #include "util/format.h" #include "main/logging.h" #include "processing.h" #include #include "assert.h" #include "physics/physics_world.h" #include "general_states.h" #include threads::ReadWriteMutex objectsLock; std::unordered_map objects; bool Object::GALAXY_CREATION = false; unsigned ObjectTypeCount = 0; extern void writeObject(net::Message& msg, Object* obj, bool includeType = true); extern Object* readObject(net::Message& msg, bool create, int knownType = -1); Object* getObjectByID(unsigned id, bool create) { { threads::ReadLock lock(objectsLock); auto obj = objects.find(id); if(obj != objects.end()) { obj->second->grab(); return obj->second; } } if(create && id != 0) { threads::WriteLock wl(objectsLock); auto obj = objects.find(id); if(obj != objects.end()) { obj->second->grab(); return obj->second; } if(create && id != 0) { Object* obj = Object::create(getObjectTypeFromID(id), 0, id); obj->grab(); return obj; } } return 0; } void invalidateUninitializedObjects() { { threads::ReadLock lock(objectsLock); foreach(it, objects) { auto* child = it->second; if(child->getFlag(objUninitialized)) child->setFlag(objValid, false); } } } ScriptObjectType* getObjectTypeFromID(unsigned id) { return getScriptObjectType(id >> ObjectTypeBitOffset); } void registerObject(Object* obj) { threads::WriteLock lock(objectsLock); objects[obj->id] = obj; } void unregisterObject(Object* obj) { threads::WriteLock lock(objectsLock); objects.erase(obj->id); } const char* obj_callback_decl[SOC_COUNT] = { "void init($1& obj)", "void postInit($1& obj)", "void destroy($1& obj)", "void groupDestroyed($1& obj)", "double tick($1& obj, double time)", "bool onOwnerChange($1& obj, Empire@ prevOwner)", "void damage($1& obj, DamageEvent& evt, double position, const vec2d& direction)", "void repair($1& obj, double amount)", "void syncInitial(const $1&, Message&)", "void syncDetailed(const $1&, Message&)", "bool syncDelta(const $1&, Message&)", "void syncInitial($1&, Message&)", "void syncDetailed($1&, Message&, double tDiff)", "void syncDelta($1&, Message&, double tDiff)", "void save($1&, SaveFile&)", "void load($1&, SaveFile&)", "void postLoad($1&)", }; static std::unordered_map objTypes; std::vector objTypeList; void clearObjects() { objects.clear(); foreach(it, objTypeList) (*it)->nextID = 1; } ScriptObjectType* getScriptObjectType(int index) { if(index >= 0 && index < (int)objTypeList.size()) return objTypeList[index]; return 0; } unsigned getScriptObjectTypeCount() { return (unsigned)objTypeList.size(); } void prepScriptObjectTypes() { foreach(it, objTypes) delete it->second; objTypes.clear(); objTypeList.clear(); auto& baseObjType = getStateDefinition("Object"); for(auto i = stateDefinitions.begin(), end = stateDefinitions.end(); i != end; ++i) { auto& type = **i; if(type.base != &baseObjType) continue; ScriptObjectType* objType = new ScriptObjectType(); objType->name = type.name; objType->script = type.scriptClass; objType->id = (unsigned)objTypeList.size(); objTypes[type.name] = objType; objTypeList.push_back(objType); } ObjectTypeCount = objTypeList.size(); } void addObjectStateValueTypes() { for(auto it = objTypes.begin(), end = objTypes.end(); it != end; ++it) { ScriptObjectType& type = *it->second; std::string stateName = type.name; std::string safeStateName = type.name+"$"; std::string scriptName = type.name+"@"; { StateValueDefinition& def = stateValueTypes[stateName.c_str()]; def = *getStateValueType("Object"); def.type = scriptName; def.returnType = scriptName; } { StateValueDefinition& def = stateValueTypes[safeStateName.c_str()]; def = *getStateValueType("Object$"); def.type = scriptName; def.returnType = scriptName; } } } void setScriptObjectStates() { const StateDefinition* objData = &getStateDefinition("Object"); for(auto it = objTypes.begin(), end = objTypes.end(); it != end; ++it) { ScriptObjectType& type = *it->second; const StateDefinition* states = &getStateDefinition(type.name); if(states == &errorStateDefinition) { states = objData; } else { if(states->base) { if(states->base != objData) { states = objData; error("ERROR: %s does not inherit from Object.", type.name.c_str()); } } else if(objData != &errorStateDefinition){ error("ERROR: %s does not inherit from Object.", type.name.c_str()); states = objData; } } type.states = states; } } void bindScriptObjectTypes() { for(auto it = objTypes.begin(), end = objTypes.end(); it != end; ++it) { ScriptObjectType& type = *it->second; //Bind script std::vector args; split(type.script, args, "::"); if(args.size() == 2) type.bind(args[0].c_str(), args[1].c_str()); } } ScriptObjectType* getScriptObjectType(const std::string& name) { auto it = objTypes.find(name); if(it == objTypes.end()) return 0; return it->second; } void Object::grab() const { ++references; } void Object::drop() const { if(--references == 0) { this->~Object(); free((void*)this); } } Object* Object::create(ScriptObjectType* type, LockGroup* lock, int id) { size_t bytes = sizeof(Object); if(type) bytes += type->states->getSize(sizeof(Object)); void* mem = malloc(bytes); return new(mem) Object(type, lock, id); } void Object::addTimedEffect(const TimedEffect& eff) { TimedEffect* timedEffect = new TimedEffect(eff); timedEffect->call(EH_Start); effects.push_back(timedEffect); } void Object::queueDeferredMessage(ObjectMessage* msg) { auto* defer = new DeferredObjMessage(); defer->msg = msg; auto* prev = deferredMessages; defer->next = prev; while(threads::compare_and_swap((void**)&deferredMessages, (void*)prev, (void*)defer) != prev) { prev = deferredMessages; defer->next = prev; } } DeferredObjMessage* Object::fetchMessages() { auto* prev = deferredMessages; while(threads::compare_and_swap((void**)&deferredMessages, (void*)prev, (void*)nullptr) != prev) prev = deferredMessages; return prev; } unsigned Object::updateVision(Object* target, unsigned depth) { unsigned mask = 0; if(target->owner && target->owner != owner) { double distSQ = position.distanceToSQ(target->position); if(distSQ < target->sightRange * target->sightRange) mask |= target->owner->mask; if(distSQ < sightRange * sightRange) target->donatedVision |= owner->mask; } if(depth > 0) for(unsigned i = 0; i < OBJ_TARGETS; ++i) mask |= updateVision(target->targets[i], depth-1); return mask; } double Object::think(double seconds) { if(!getFlag(objValid)) return 5.0; setFlag(objWakeUp, false); vec3d prevPos = position; double objDelay = 5.0; if(type && script) { if(auto* scr_tick = type->functions[SOC_tick]) { scripts::Call cl = devices.scripts.server->call(scr_tick); if(cl.valid()) { cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push(seconds); if(!cl.call(objDelay)) objDelay = 5.0; } } } if(getFlag(objQueueDestroy)) { destroy(); return 5.0; } //Calculate vision updates if(!devices.network->isClient) { if(alwaysVisible) { visibleMask = sightedMask = ~0x0; } //Find nearby objects we can see else { if(owner && owner->valid() && sightRange > 0.0) { unsigned sightCheckSteps = unsigned(100.0 * seconds); if(sightDelay <= sightCheckSteps) { unsigned mask = owner->mask; vec3d from = position; double dist = sightRange; devices.physics->findInBox(AABBoxd::fromCircle(position,sightRange), [mask,from,dist](const PhysicsItem& item) { if(item.type != PIT_Object) return; Object* other = item.object; if(other->alwaysVisible || (other->donatedVision & mask)) return; double distance = from.distanceToSQ(other->position); if(distance < (dist + other->radius) * (dist + other->radius)) { if(distance < (other->seeableRange + other->radius) * (other->seeableRange + other->radius)) other->donatedVision |= mask; } }, ~owner->mask ); //Delay psuedo-randomly by 1.5-2.8 seconds (vision persists for 3 seconds) sightDelay = 150 + id % 64; } else { sightDelay -= sightCheckSteps; } } //Find nearby objects that can see us unsigned newVisionMask = 1; if(owner && owner->visionMask) { newVisionMask |= donatedVision | owner->mask; donatedVision = owner->mask; } else { newVisionMask |= donatedVision; } unsigned sightMask = newVisionMask; unsigned sightDecaySteps = (unsigned)(seconds * (255.0 / 3.0)); unsigned char decay = sightDecaySteps >= 255 ? 255 : sightDecaySteps; for(unsigned i = 0, mask = 2; i < validEmpireCount; ++i, mask <<= 1) { unsigned char& v = visionTimes[i]; if((newVisionMask & currentVision[i]) != 0) { v = 255; sightMask |= mask; } else if(v > decay) { v -= decay; newVisionMask |= mask; } else { v = 0; } } visibleMask = newVisionMask; sightedMask |= sightMask; } } else if(alwaysVisible) { visibleMask = sightedMask = ~0x0; } //Tick effects { unsigned cnt = (unsigned)effects.size(); for(unsigned i = 0; i < cnt; ++i) { TimedEffect& eff = *effects[i]; EffectStatus pre = eff.event.status; //Set tick values eff.tick(seconds); //Call suspend, continue and end if(eff.event.status == ES_Ended) { eff.call(EH_End); delete &eff; if(effects.size() > 1) { effects[i] = effects[effects.size()-1]; if(effects.size() <= cnt) { --i; --cnt; } effects.resize(effects.size() - 1); } else { effects.resize(0); break; } } else { if(pre == ES_Suspended) { if(eff.event.status != ES_Suspended) eff.call(EH_Continue); } else { if(eff.event.status == ES_Suspended) eff.call(EH_Suspend); } } } } if(physItem) { if(!group) { //Update physics positon (this can move across a grid boundary, which needs access to the world) if(position != prevPos) devices.physics->updateItem(*physItem, AABBoxd::fromCircle(position, radius)); } else { //Update our position without informing the group if(position != prevPos) physItem->bound = AABBoxd::fromCircle(position, radius); //If we are the group's owner, update the group to match the members' locations (touches world) if(group->getOwner() == this) group->update(); } } if(devices.network->isServer && devices.network->hasSyncedClients) { unsigned newEmpireVisibleMask = 0; for(unsigned i = 0, cnt = Empire::getEmpireCount(); i < cnt; ++i) { Empire* emp = Empire::getEmpireByIndex(i); if(!emp->valid()) continue; auto* pl = emp->player; if(!pl || !pl->wantsDeltas) continue; if(emp->visionMask & visibleMask) newEmpireVisibleMask |= emp->mask; } //Inform about vision changes if(newEmpireVisibleMask != prevVisibleMask) { devices.network->sendObjectVisionDelta(this, prevVisibleMask, newEmpireVisibleMask); prevVisibleMask = newEmpireVisibleMask; } } return objDelay; } void Object::damage(DamageEvent& evt, double position, const vec2d& direction) { if(type && script) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_damage]); if(cl.valid()) { cl.setObject(script); cl.push((void*)this); cl.push((void*)&evt); cl.push(position); cl.push((void*)&direction); cl.call(); } } } void Object::repair(double amount) { if(type && script) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_repair]); if(cl.valid()) { cl.setObject(script); cl.push((void*)this); cl.push(amount); cl.call(); } } } Object::Object(ScriptObjectType* Type, LockGroup* group, int ID) : references(1), id(0), group(0), lockGroup(group ? group : getRandomLock()), originalLock(0), lockHint(0), deferredMessages(0), owner(Empire::getDefaultEmpire()), alwaysVisible(false), sightRange(1500.f), seeableRange(FLT_MAX), visionTimes(), visibleMask(0), donatedVision(0), prevVisibleMask(0), sightedMask(0), sightDelay(0), flags(objValid | objUninitialized), physItem(0), radius(0), lastTick(devices.driver->getGameTime()), node(0), script(0), type(Type) { if(ID != 0) { id = ID; } else { id = type->nextID++; id |= type->id << 26; } //Vary the first tick by roughly a quarter second psuedo-randomly nextTick = lastTick + ((double)(unsigned char)ID)/1000.0; registerObject(this); for(unsigned i = 0; i < OBJ_TARGETS; ++i) targets[i] = this; //Dynamic memory is always a full object size past 'this' if(Type) memset(this + 1, 0, Type->states->getSize(sizeof(Object))); } Object::~Object() { if(type) { void* mixinMem = this + 1; type->states->unprepare(mixinMem); } } bool Object::isVisibleTo(Empire* emp) const { return (emp->visionMask & visibleMask) != 0; } bool Object::isKnownTo(Empire* emp) const { return (emp->mask & sightedMask) != 0; } bool Object::isLocked() const { return getActiveLockGroup() == lockGroup || !processing::isRunning() || getFlag(objUninitialized); } void Object::init() { if(type) { void* mixinMem = this + 1; type->states->prepare(mixinMem); script = type->create(); lockGroup->add(this); setFlag(objUninitialized, false); scripts::Call cl = devices.scripts.server->call(type->functions[SOC_init]); if(cl.valid() && script) { cl.setObject(script); cl.push((void*)this); cl.call(); } } } void Object::postInit() { if(type) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_postInit]); if(cl.valid() && script) { cl.setObject(script); cl.push((void*)this); cl.call(); } } } void Object::setOwner(Empire* newOwner) { if(newOwner == owner) return; Empire* prevOwner = owner; owner = newOwner; if(owner) visibleMask |= owner->mask; if(type) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_ownerChange]); if(cl.valid() && script) { cl.setObject(script); cl.push((void*)this); cl.push((void*)prevOwner); bool ret = false; cl.call(ret); if(ret) owner = prevOwner; } } if(owner != prevOwner) { if(prevOwner) prevOwner->unregisterObject(this); if(owner) owner->registerObject(this); if(!GALAXY_CREATION) setFlag(objSendDelta, true); if(physItem) devices.physics->updateItem(*physItem, AABBoxd::fromCircle(position, radius)); } } void Object::clearScripts() { assert(!getFlag(objValid)); //Kill the script object if(script) { script->Release(); script = nullptr; } //Clear all references in the states if(type) { void* mixinMem = this + 1; type->states->preClear(mixinMem); } } void Object::destroy(bool fromUniverse) { if(getFlag(objValid)) { setFlag(objStopTicking, true); //Inform network clients if(devices.network->isServer) devices.network->destroyObject(this); //Inform the script of the destruction if(type && !fromUniverse && script) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_destroy]); if(cl.valid()) { cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.call(); } } //Invalidate the object setFlag(objValid, false); //Clear the physics item if(!group) { if(physItem) { devices.physics->unregisterItem(*physItem); auto* it = physItem; physItem = nullptr; delete it; } } else { if(physItem) { physItem->object = 0; physItem = 0; } if(group->getOwner() == this) { if(group->removeOwner()) { scripts::Call cl = devices.scripts.server->call(type->functions[SOC_groupDestroyed]); if(cl.valid() && script) { cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.call(); } } } group->drop(); group = 0; //Clear phys item's reference drop(); } //Stop all timed effects for(unsigned i = 0, cnt = effects.size(); i < cnt; ++i) { TimedEffect* eff = effects[i]; eff->call(EH_Destroy); delete eff; } //Remove object from universe if(!fromUniverse) devices.universe->removeChild(this); //Clear the object's graphics if(node) { node->markForDeletion(); node = 0; } if(fromUniverse) clearScripts(); else queueObjectClear(this); //Remove object from empire if(owner) { if(!fromUniverse) owner->unregisterObject(this); else drop(); } if(!fromUniverse) unregisterObject(this); drop(); } } bool Object::getFlag(ObjectFlag flag) const { return (flags & flag) != 0; } void Object::setFlag(ObjectFlag flag, bool value) { if(value) flags |= flag; else flags &= ~flag; } bool Object::setFlagSecure(ObjectFlag flag, bool flagOn) { int prevVal, newVal; do { //If the flag is already set, give up prevVal = flags; if(((prevVal & flag) != 0) == flagOn) return false; //See what the new value would be, and keep trying until we are sure of the result newVal = flagOn ? (prevVal | flag) : (prevVal & ~flag); } while(flags.compare_exchange_strong(newVal, prevVal) != prevVal); return true; } bool Object::isValid() const { return getFlag(objValid); } bool Object::isFocus() const { return getFlag(objFocus); } void Object::focus() { setFlag(objFocus, true); //TODO: Keep a global list of focused objects } bool Object::isInitialized() const { return !getFlag(objUninitialized); } void Object::wake() { setFlag(objWakeUp,true); } void Object::sleep(double seconds) { nextTick += seconds; } void Object::updateTargets() { //Non-physical objects don't track targets. //However, we may load an old save where they did, so try to collect our pointers. if(getFlag(objNoPhysics)) { unsigned index = OBJ_TARGETS; for(unsigned i = 0; i < OBJ_TARGETS; ++i) { if(targets[i] != this) { index = i; break; } } if(index == OBJ_TARGETS) return; auto& targ = targets[index]; for(unsigned i = 0; i < OBJ_TARGETS; ++i) { if(targ->targets[i] == this) { targ.swap(targ->targets[i]); break; } } return; } //Identify our worst target and try to locate an object to send it to double worst = position.distanceToSQ(targets[0]->position); unsigned worstIndex = 0; for(unsigned i = 1; i < OBJ_TARGETS; ++i) { double d = position.distanceToSQ(targets[i]->position); if(d > worst) { worst = d; worstIndex = i; } } auto& worstTarget = targets[worstIndex]; auto& targPos = worstTarget->position; double bestNet = 0; heldPointer* best = 0; //Look for the best net difference we can achieve via this swap for(unsigned i = 0; i < OBJ_TARGETS; ++i) { heldPointer& targ = targets[i]; if(targ == this) { devices.universe->setRandomTarget(targ); continue; } else if(i != worstIndex && targ != worstTarget) { for(unsigned j = 0; j < OBJ_TARGETS; ++j) { heldPointer& test = targ->targets[j]; if(test == this) continue; double d = position.distanceToSQ(test->position); double o_d = targ->position.distanceToSQ(test->position); double o_targ_d = targ->position.distanceToSQ(targPos); double our_new = d - worst, their_new = o_targ_d - o_d; double net = our_new + their_new; if(net < bestNet) { best = &test; bestNet = net; } } } } //If we have a better object to swap with, do so. Otherwise, occasionally swap to a random target to prevent stagnation if(best) worstTarget.swap(*best); } inline double sqr(double x) { return x*x; } static inline void sendDetails(net::Message& msg, Object& obj, bool sendStates = true, bool sendPosition = true) { if(obj.owner) msg << obj.owner->id; else msg << INVALID_EMPIRE; msg << obj.alwaysVisible; msg << obj.visibleMask; msg << obj.sightRange; msg << obj.seeableRange; if(sendPosition) { if(obj.velocity.getLengthSQ() < 0.001) { msg.write1(); msg.writeMedVec3(obj.position.x, obj.position.y, obj.position.z); } else { msg.write0(); } } if(sendStates) obj.type->states->syncWrite(msg, &obj + 1); int count = obj.stats.size(); msg.writeSmall(count); obj.stats.iterateAll([&msg,&count](uint64_t id, int64_t value) { if(count <= 0) return; int type = int((id & 0xFF00000000000000) >> 56); int left = int((id & 0x00FFFFFF00000000) >> 32); int right = int((id & 0x00000000FFFFFFFF)); msg.writeSmall(type); msg.writeSmall(left); msg.writeSmall(right); msg << value; count--; }); } static inline void recvDetails(net::Message& msg, Object& obj, bool recvStates = true, bool recvPosition = true) { unsigned char empID; msg >> empID; Empire* newOwner = Empire::getEmpireByID(empID); if(newOwner != obj.owner) { if(obj.getFlag(objUninitialized)) obj.owner = newOwner; else obj.setOwner(newOwner); } msg >> obj.alwaysVisible; msg >> obj.visibleMask; obj.sightedMask |= obj.visibleMask; msg >> obj.sightRange; msg >> obj.seeableRange; if(recvPosition) { if(msg.readBit()) { msg.readMedVec3(obj.position.x, obj.position.y, obj.position.z); obj.velocity = vec3d(); obj.acceleration = vec3d(); } } if(recvStates) obj.type->states->syncRead(msg, &obj + 1); int count = msg.readSmall(); for(int i = 0; i < count; ++i) { int type = msg.readSmall(); int left = msg.readSmall(); int right = msg.readSmall(); int64_t value; msg >> value; obj.stats.set(uint64_t(type) << 56 | uint64_t(left) << 32 | uint64_t(right), value); } //TODO: Optimize with changes? } void Object::sendInitial(net::Message& msg) { assert(devices.network->isServer); writeObject(msg, this); msg << (float)radius; msg << name; msg << sightedMask; sendDetails(msg, *this, false, false); msg.writeMedVec3(position.x, position.y, position.z); if(velocity.zero()) { msg.write0(); } else { msg.write1(); msg.writeSmallVec3(velocity.x, velocity.y, velocity.z); } if(acceleration.zero()) { msg.write0(); } else { msg.write1(); msg.writeSmallVec3(acceleration.x, acceleration.y, acceleration.z); } msg.writeRotation(rotation.xyz.x, rotation.xyz.y, rotation.xyz.z, rotation.w); msg.writeBit(getFlag(objNoPhysics)); msg.writeBit(getFlag(objMemorable)); msg.writeBit(getFlag(objNoDamage)); //TODO: Group sync is hackish, not finalized //due to groups probably changing in the future if(group) { msg.write1(); msg << group->id; msg << group->getObjectCount(); msg.writeBit(group->getOwner() == this); } else { msg.write0(); } type->states->syncWrite(msg, this + 1); msg.writeAlign(); auto* func = type->functions[SOC_syncInitial]; if(func && script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push((void*)&msg); cl.call(); } } void Object::sendDetailed(net::Message& msg) { assert(devices.network->isServer); sendDetails(msg, *this); auto* func = type->functions[SOC_syncDetailed]; if(func && script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push((void*)&msg); cl.call(); } } bool Object::sendDelta(net::Message& msg) { assert(devices.network->isServer); //Check if we're flagged to send bool send = false; if(getFlag(objSendDelta)) { msg.write1(); sendDetails(msg, *this); send = true; setFlag(objSendDelta, false); } else { msg.write0(); } //See if we should sync any stats if(stats.hasDirty()) { msg.write1(); send = true; int count = stats.getDirtyCount(); msg.writeSmall(count); stats.handleDirty([&msg,&count](uint64_t id, int64_t value) -> bool { if(count <= 0) return false; int left = int((id & 0xFFFFFFFF00000000) >> 32); int right = int((id & 0x00000000FFFFFFFF)); msg.writeSmall(left); msg.writeSmall(right); msg << value; count--; return true; }); } else { msg.write0(); } //See if the scripts want to send anything auto* func = type->functions[SOC_syncDelta]; if(func && script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push((void*)&msg); bool scriptsend = false; cl.call(scriptsend); if(scriptsend) send = true; } return send; } void Object::recvDetailed(net::Message& msg, double fromTime) { assert(devices.network->isClient); recvDetails(msg, *this); auto* func = type->functions[SOC_recvDetailed]; if(func && script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push((void*)&msg); cl.push(fromTime - lastTick); cl.call(); } } void Object::recvDelta(net::Message& msg, double fromTime) { assert(devices.network->isClient); if(msg.readBit()) recvDetails(msg, *this); if(msg.readBit()) { int count = msg.readSmall(); for(int i = 0; i < count; ++i) { int left = msg.readSmall(); int right = msg.readSmall(); int64_t value; msg >> value; stats.set(uint64_t(left) << 32 | uint64_t(right), value); } } auto* func = type->functions[SOC_recvDelta]; if(func && script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)script); cl.push((void*)this); cl.push((void*)&msg); cl.push(fromTime - lastTick); cl.call(); } } extern threads::ReadWriteMutex groupIDsLock; Object* recvObjectInitial(net::Message& msg, double fromTime) { assert(devices.network->isClient); Object* obj = readObject(msg, true); float rad = 1.f; msg >> rad; obj->radius = rad; msg >> obj->name; msg >> obj->sightedMask; recvDetails(msg, *obj, false, false); msg.readMedVec3(obj->position.x, obj->position.y, obj->position.z); if(msg.readBit()) msg.readSmallVec3(obj->velocity.x, obj->velocity.y, obj->velocity.z); if(msg.readBit()) msg.readSmallVec3(obj->acceleration.x, obj->acceleration.y, obj->acceleration.z); obj->lastTick = fromTime; obj->nextTick = obj->lastTick; msg.readRotation(obj->rotation.xyz.x, obj->rotation.xyz.y, obj->rotation.xyz.z, obj->rotation.w); obj->setFlag(objNoPhysics, msg.readBit()); obj->setFlag(objMemorable, msg.readBit()); obj->setFlag(objNoDamage, msg.readBit()); //TODO: Group sync is hackish, not finalized //due to groups probably changing in the future if(msg.readBit()) { unsigned inGroup = 0; LockGroup* locked = 0; int groupId; msg >> groupId; unsigned objCount; msg >> objCount; groupIDsLock.writeLock(); obj->setFlag(objStopTicking, true); ObjectGroup* group = ObjectGroup::byID(groupId); if(!group) { group = new ObjectGroup(objCount, groupId); group->grab(); group->grab(); groupIDsLock.release(); group->setObject(0, obj); group->grab(); locked = lockObject(obj); } else { groupIDsLock.release(); locked = lockObject(group->getOwner()); inGroup = group->setNextObject(obj); } if(msg.readBit()) { group->setOwner(obj); unlockObject(locked); locked = lockObject(obj); } obj->group = group; if(!obj->getFlag(objNoPhysics)) { obj->physItem = group->getPhysicsItem(inGroup); obj->physItem->bound = AABBoxd::fromCircle(obj->position, obj->radius); obj->physItem->gridLocation = 0; obj->physItem->type = PIT_Object; obj->physItem->object = obj; } obj->grab(); if(inGroup == group->getObjectCount() - 1) { group->postInit(); for(unsigned i = 0, cnt = group->getObjectCount(); i < cnt; ++i) { Object* mem = group->getObject(i); if(mem->isValid()) mem->setFlag(objStopTicking, false); mem->drop(); } } unlockObject(locked); } else if(!obj->getFlag(objNoPhysics)) { obj->physItem = devices.physics->registerItem(AABBoxd::fromCircle(obj->position, obj->radius), obj); } ObjectLock lock(obj); if(obj->owner) obj->owner->registerObject(obj); devices.universe->addChild(obj); obj->init(); obj->type->states->syncRead(msg, obj + 1); msg.readAlign(); auto* func = obj->type->functions[SOC_recvInitial]; if(func && obj->script) { scripts::Call cl = devices.scripts.server->call(func); cl.setObject((asIScriptObject*)obj->script); cl.push((void*)obj); cl.push((void*)&msg); cl.call(); } obj->drop(); return obj; } ObjectLock::ObjectLock(Object* Obj, bool priority) : released(false) { group = lockObject(Obj, priority); } ObjectLock::~ObjectLock() { if(!released) unlockObject(group); } void ObjectLock::release() { unlockObject(group); released = true; } ScriptObjectType::ScriptObjectType() : nextID(1), states(0), scriptType(0), blueprintOffset(0) { for(int i = 0; i < SOC_COUNT; ++i) functions[i] = 0; } void ScriptObjectType::bind(const char* module, const char* decl) { //Make sure the type exists, and is a script type scriptType = devices.scripts.server->getClass(module, decl); if(scriptType == 0) { error("Type %s::%s was not found", module, decl); return; } else if((scriptType->GetTypeId() & asTYPEID_SCRIPTOBJECT) == 0) { error("Type %s is not a script type", decl); scriptType = 0; return; } //Bind system methods for(int i = 0; i < SOC_COUNT; ++i) functions[i] = scriptType->GetMethodByDecl(format(obj_callback_decl[i],name).c_str()); //Bind type methods if(states) { for(size_t i = 0; i < states->methods.size(); ++i) { auto& m = states->methods[i]; auto* method = m.wrapped; if(!method) continue; method->objTypeId = id; if(!method->func) method->origDesc = method->desc; scripts::GenericCallDesc desc = method->origDesc; if(method->wrapped.constFunction) { desc.constFunction = true; method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) continue; //Prepend the containing object to see if we need it desc.prepend(scripts::ArgumentDesc(scripts::GT_Object_Ref, method->wrapped.constFunction)); method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) { method->desc = desc; method->passContaining = true; continue; } //Prepend the player to see if we need it desc.prepend(scripts::GT_Player_Ref); method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) { method->desc = desc; method->passContaining = true; method->passPlayer = true; continue; } desc = method->origDesc; desc.constFunction = false; method->passContaining = false; method->passPlayer = false; } method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) continue; //Prepend the containing object to see if we need it desc.prepend(scripts::ArgumentDesc(refType, method->wrapped.constFunction)); method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) { method->desc = desc; method->passContaining = true; continue; } //Prepend the player to see if we need it desc.prepend(scripts::GT_Player_Ref); method->func = scriptType->GetMethodByDecl(desc.declaration().c_str()); if(method->func) { method->desc = desc; method->passContaining = true; method->passPlayer = true; continue; } if(!devices.network->isClient || method->shadow || (method->local && !method->server)) error("Could not find %s method '%s'", name.c_str(), desc.declaration().c_str()); } } } asIScriptObject* ScriptObjectType::create() const { asIScriptObject* ptr = 0; if(scriptType) { asIScriptFunction* func = scriptType->GetFactoryByIndex(0); scripts::Call cl = devices.scripts.server->call(func); cl.call(ptr); if(ptr) ptr->AddRef(); } return ptr; }