#include "Entity.h" #include #include "BlockType.h" #include "Dimension.h" #include "EntityRemovedUpdate.h" #include "Game.h" #include "ItemSkill.h" #include "ItemStack.h" #include "NoBlock.h" ActionTarget::ActionTarget(Framework::Vec3 blockPos, Direction blockSide) : blockPos(blockPos), targetBlockSide(blockSide), entityId(-1) {} ActionTarget::ActionTarget(int entityId) : entityId(entityId) {} bool ActionTarget::isBlock( Framework::Vec3 blockPos, Direction blockSide) const { return this->entityId == -1 && this->blockPos == blockPos && (this->targetBlockSide == targetBlockSide || blockSide == NO_DIRECTION); } bool ActionTarget::isEntity(int entityId) const { return this->entityId == entityId; } bool ActionTarget::useItemSkillOnTarget( Entity* zActor, ItemSkill* zItemSkill, Item* zUsedItem) { if (entityId >= 0) { Entity* target = Game::INSTANCE->zEntity(entityId); if (target) { return zItemSkill->use(zActor, zUsedItem, target); } } else { Block* block = Game::INSTANCE->zRealBlockInstance( blockPos, zActor->getDimensionId()); if (block) { return zItemSkill->use(zActor, zUsedItem, block); } } return 0; } bool ActionTarget::interactItemSkillOnTarget( Entity* zActor, ItemSkill* zItemSkill, Item* zUsedItem) { if (zItemSkill) { if (entityId >= 0) { Entity* target = Game::INSTANCE->zEntity(entityId); if (target) return zItemSkill->interact(zActor, zUsedItem, target); } else { Block* block = Game::INSTANCE->zRealBlockInstance( blockPos, zActor->getDimensionId()); if (block) return zItemSkill->interact(zActor, zUsedItem, block); } } else { if (entityId >= 0) { Block* block = Game::INSTANCE->zRealBlockInstance( blockPos, zActor->getDimensionId()); if (block) return block->interact(zUsedItem, zActor); } else { Block* block = Game::INSTANCE->zRealBlockInstance( blockPos, zActor->getDimensionId()); if (block) return block->interact(zUsedItem, zActor); } } return 0; } bool ActionTarget::placeBlock(Entity* zActor, Item* zItem) { if (zActor->getStamina() > 0.2f) { if (zItem->canBePlacedAt(zActor->getDimensionId(), blockPos + getDirection(targetBlockSide))) { Block* block = zItem->zPlacedBlockType()->createBlockAt( blockPos + getDirection(targetBlockSide), zActor->getDimensionId(), zItem); if (block) { Game::INSTANCE->zDimension(zActor->getDimensionId()) ->placeBlock(block->getPos(), block); zItem->onPlaced(); zActor->setStamina(zActor->getStamina() - 0.2f); return 1; } } } return 0; } void ActionTarget::toMessage( const ActionTarget* zTarget, int dimensionId, NetworkMessage* zMsg) { if (zTarget) { if (zTarget->entityId >= 0) { char* message = new char[6]; message[0] = 3; message[1] = 1; *(int*)(message + 2) = zTarget->entityId; zMsg->setMessage(message, 6); } else { Framework::Text targetUIML = ""; auto block = Game::INSTANCE->zBlockAt(zTarget->blockPos, dimensionId); if (block.isA()) { targetUIML = block.getA()->getTargetUIML(); } else if (block.isB()) { targetUIML = Game::INSTANCE->zBlockType(block.getB())->getTargetUIML(); } char* message = new char[18 + targetUIML.getLength() + 2]; message[0] = 3; message[1] = 2; *(int*)(message + 2) = zTarget->blockPos.x; *(int*)(message + 6) = zTarget->blockPos.y; *(int*)(message + 10) = zTarget->blockPos.z; *(int*)(message + 14) = zTarget->targetBlockSide; short len = (short)targetUIML.getLength(); *(short*)(message + 18) = len; memcpy(message + 20, targetUIML.getText(), len); zMsg->setMessage(message, 18 + len + 2); } } else { char* message = new char[2]; message[0] = 3; message[1] = 0; zMsg->setMessage(message, 2); } } void ActionTarget::save(ActionTarget* zTarget, Framework::StreamWriter* zWriter) { if (zTarget) { if (zTarget->entityId >= 0) { char b = 1; zWriter->schreibe(&b, 1); zWriter->schreibe((char*)&zTarget->entityId, 4); } else { char b = 2; zWriter->schreibe(&b, 1); zWriter->schreibe((char*)&zTarget->blockPos.x, 4); zWriter->schreibe((char*)&zTarget->blockPos.y, 4); zWriter->schreibe((char*)&zTarget->blockPos.z, 4); zWriter->schreibe((char*)&zTarget->targetBlockSide, 4); } } else { char b = 0; zWriter->schreibe(&b, 1); } } ActionTarget* ActionTarget::load(Framework::StreamReader* zReader) { char b; zReader->lese(&b, 1); if (b == 1) { int id; zReader->lese((char*)&id, 4); return new ActionTarget(id); } else if (b == 2) { Framework::Vec3 pos; Direction side; zReader->lese((char*)&pos.x, 4); zReader->lese((char*)&pos.y, 4); zReader->lese((char*)&pos.z, 4); zReader->lese((char*)&side, 4); return new ActionTarget(pos, side); } return 0; } Entity::Entity( int typeId, Framework::Vec3 location, int dimensionId, int entityId) : Inventory(location, dimensionId, true), chatSecurityLevel(0), speed(0, 0, 0), faceDir(1, 0, 0), target(0), typeId(typeId), removed(0), gravityMultiplier(1.f), jumpSpeed(0.f), id(entityId), placeBlockCooldown(0) {} void Entity::onDeath() { if (!removed) { Game::INSTANCE->requestWorldUpdate( new EntityRemovedUpdate(id, dimensionId, location)); removed = 1; } } bool Entity::useItem(int typeId, ItemStack* zStack, bool left) { if (left) { if (!zStack || !zStack->zItem() || zStack->zItem()->isUsable()) { cs.lock(); if (target) { ItemSkill* selected = zSkill(typeId); if (!selected) { selected = Game::INSTANCE->zItemType(typeId) ->createDefaultItemSkill(); selected->setItemTypeId(typeId); if (selected) skills.add(selected); } if (!selected) { selected = zSkill(ItemTypeEnum::PLAYER_HAND); selected->setItemTypeId(ItemTypeEnum::PLAYER_HAND); } bool result = target->useItemSkillOnTarget(this, selected, !zStack || zStack->getSize() > 1 ? 0 : (Item*)zStack->zItem()); cs.unlock(); return result; } cs.unlock(); } else { useItem(ItemTypeEnum::PLAYER_HAND, 0, left); } } else { if (zStack && zStack->zItem() && zStack->zItem()->isPlaceable() && zStack->getSize() > 0) { // place item cs.lock(); if (target) { if (placeBlockCooldown <= 0) { Item* item = zStack->extractFromStack(); bool result = target->placeBlock(this, item); if (item->getHp() > 0) { if (!zStack->addToStack( dynamic_cast(item->getThis()))) { ItemStack* newStack = new ItemStack(item, 1); addItems(newStack, NO_DIRECTION, 0); if (newStack->getSize()) { Game::INSTANCE->spawnItem( location, dimensionId, newStack); } } else { item->release(); } } else { item->release(); } if (result) { placeBlockCooldown = 15; } cs.unlock(); return result; } else { cs.unlock(); return 0; } } cs.unlock(); } if (zStack && zStack->zItem() && zStack->zItem()->isEatable() && zStack->getSize() > 0) { // eat item if (zStack->getSize() == 1) { return ((Item*)zStack->zItem())->applyFoodEffects(this); } else { if (zStack->zItem()->canApplyFoodEffectsFully(this)) { Item* item = zStack->extractFromStack(); item->applyFoodEffects(this); item->release(); return 1; } } } if (!zStack || !zStack->zItem() || zStack->zItem()->isUsable()) { cs.lock(); if (target) { ItemSkill* selected = zSkill(typeId); if (!selected) { selected = Game::INSTANCE->zItemType(typeId) ->createDefaultItemSkill(); selected->setItemTypeId(typeId); if (selected) skills.add(selected); } if (!selected) { selected = zSkill(ItemTypeEnum::PLAYER_HAND); selected->setItemTypeId(ItemTypeEnum::PLAYER_HAND); } bool result = target->interactItemSkillOnTarget(this, selected, !zStack || zStack->getSize() > 1 ? 0 : (Item*)zStack->zItem()); cs.unlock(); return result; } cs.unlock(); } else { useItem(ItemTypeEnum::PLAYER_HAND, 0, left); } } return 0; } void Entity::onTargetChange() {} bool Entity::interact(Item* zItem, Entity* zActor) { return false; } void Entity::addMovementFrame(MovementFrame& frame) { cs.lock(); movements.add(frame); cs.unlock(); NetworkMessage* message = new NetworkMessage(); message->addressEntity(this); char* msg = new char[37]; msg[0] = 0; *(float*)(msg + 1) = frame.direction.x; *(float*)(msg + 5) = frame.direction.y; *(float*)(msg + 9) = frame.direction.z; *(float*)(msg + 13) = frame.targetPosition.x; *(float*)(msg + 17) = frame.targetPosition.y; *(float*)(msg + 21) = frame.targetPosition.z; *(int*)(msg + 25) = frame.movementFlags; *(double*)(msg + 29) = frame.duration; message->setMessage(msg, 37); Game::INSTANCE->broadcastMessage(message); faceDir = frame.direction; // TODO implement subscription system to notify only interested clients } void Entity::calculateTarget(Framework::Vec3 basePos, Framework::Vec3 direction, const Item* zItem) { Framework::Vec3 headPosition = basePos + faceOffset; int px = (int)floor(headPosition.x); int py = (int)floor(headPosition.y); int pz = (int)floor(headPosition.z); direction.normalize(); Direction dir = BOTTOM; while (true) { if (getDefaultBlock(Game::INSTANCE->zBlockAt( Framework::Vec3{px, py, pz}, dimensionId)) ->isInteractable(zItem)) { if (!target || !target->isBlock({px, py, pz}, dir)) { cs.lock(); delete target; target = new ActionTarget({px, py, pz}, dir); cs.unlock(); onTargetChange(); } break; } // collision to neighbor of current block if (direction.x > 0) { float xt = ((float)px + 1.f - headPosition.x) / direction.x; Framework::Vec3 tmp = headPosition + direction * xt; if (xt <= targetDistanceLimit && tmp.y >= (float)py && tmp.y < (float)py + 1.f && tmp.z >= (float)pz && tmp.z < (float)pz + 1.f) { dir = WEST; px++; continue; } } if (direction.x < 0) { float xt = ((float)px - headPosition.x) / direction.x; Framework::Vec3 tmp = headPosition + direction * xt; if (xt <= targetDistanceLimit && tmp.y >= (float)py && tmp.y < (float)py + 1.f && tmp.z >= (float)pz && tmp.z < (float)pz + 1.f) { dir = EAST; px--; continue; } } if (direction.y > 0) { float yt = ((float)py + 1.f - headPosition.y) / direction.y; Framework::Vec3 tmp = headPosition + direction * yt; if (yt <= targetDistanceLimit && tmp.x >= (float)px && tmp.x < (float)px + 1.f && tmp.z >= (float)pz && tmp.z < (float)pz + 1.f) { dir = NORTH; py++; continue; } } if (direction.y < 0) { float yt = ((float)py - headPosition.y) / direction.y; Framework::Vec3 tmp = headPosition + direction * yt; if (yt <= targetDistanceLimit && tmp.x >= (float)px && tmp.x < (float)px + 1.f && tmp.z >= (float)pz && tmp.z < (float)pz + 1.f) { dir = SOUTH; py--; continue; } } if (direction.z > 0) { float zt = ((float)pz + 1.f - headPosition.z) / direction.z; Framework::Vec3 tmp = headPosition + direction * zt; if (zt <= targetDistanceLimit && tmp.x >= (float)px && tmp.x < (float)px + 1.f && tmp.y >= (float)py && tmp.y < (float)py + 1.f) { dir = BOTTOM; pz++; continue; } } if (direction.z < 0) { float zt = ((float)pz - headPosition.z) / direction.z; Framework::Vec3 tmp = headPosition + direction * zt; if (zt <= targetDistanceLimit && tmp.x >= (float)px && tmp.x < (float)px + 1.f && tmp.y >= (float)py && tmp.y < (float)py + 1) { dir = TOP; pz--; continue; } } if (target) { cs.lock(); delete target; target = 0; cs.unlock(); onTargetChange(); } break; } } void Entity::removeStatusBarObserver(Entity* zSource, Framework::Text id) { cs.lock(); int index = 0; for (auto observer : statusBarObservers) { if (observer.getFirst() == zSource->getId() && observer.getSecond().istGleich(id)) { statusBarObservers.remove(index); break; } index++; } cs.unlock(); } void Entity::addStatusBarObserver(Entity* zSource, Framework::Text id) { cs.lock(); for (auto observer : statusBarObservers) { if (observer.getFirst() == zSource->getId() && observer.getSecond().istGleich(id)) { cs.unlock(); return; } } statusBarObservers.add( Framework::ImmutablePair(zSource->getId(), id)); cs.unlock(); } void Entity::notifyStatusBarObservers(NetworkMessage* msg) { cs.lock(); int index = 0; Framework::Array toDelete; for (auto observer : statusBarObservers) { Entity* e = Game::INSTANCE->zEntity(observer.getFirst()); if (e) { msg->addressUIElement(observer.getSecond()); Game::INSTANCE->sendMessage(msg->clone(), e); } else toDelete.add(index, 0); index++; } for (int i : toDelete) statusBarObservers.remove(i); cs.unlock(); msg->release(); } ItemSkill* Entity::zSkill(int itemType) { for (ItemSkill* skill : skills) { if (skill->getItemTypeId() == itemType) { return skill; } } return 0; } void Entity::prepareTick(const Dimension* zDimension) {} void Entity::tick(const Dimension* zDimension) { if (placeBlockCooldown > 0) { placeBlockCooldown--; } placeBlockCooldown--; if (time.isMeasuring()) { time.messungEnde(); if (movements.getEintragAnzahl() > 0) { MovementFrame currentFrame = movements.get(0); double seconds = time.getSekunden(); while (seconds > 0) { if (currentFrame.duration <= 0) { cs.lock(); movements.remove(0); cs.unlock(); if (movements.getEintragAnzahl() > 0) currentFrame = movements.get(0); else break; } double t = MIN(currentFrame.duration, seconds); // TODO: add collision detection to reduce cheating capability location += (currentFrame.targetPosition - location) * (float)(t / currentFrame.duration); currentFrame.duration -= t; seconds -= t; if (currentFrame.duration <= 0) { location = currentFrame.targetPosition; } } if (currentFrame.duration > 0) movements.set(currentFrame, 0); if (getStamina() <= getMaxStamina() - 0.0025f) { if (getThirst() > 0 && getHunger() > 0) { setStamina(getStamina() + 0.0025f); setHunger(getHunger() - 0.0005f); setThirst(getThirst() - 0.0015f); } } } else { if (getStamina() <= getMaxStamina() - 0.005f) { if (getThirst() > 0 && getHunger() > 0) { setStamina(getStamina() + 0.005f); setHunger(getHunger() - 0.001f); setThirst(getThirst() - 0.003f); } } } } time.messungStart(); } void Entity::api(Framework::StreamReader* zRequest, NetworkMessage* zResponse, Entity* zSource) { char type; zRequest->lese(&type, 1); switch (type) { case 0: // request status bar state { char len; zRequest->lese(&len, 1); char* guiId = new char[(int)len + 1]; zRequest->lese(guiId, len); guiId[(int)len] = 0; zResponse->addressUIElement(guiId); addStatusBarObserver(zSource, guiId); char* msg = new char[33]; msg[0] = 0; *(float*)(msg + 1) = getMaxHP(); *(float*)(msg + 5) = getCurrentHP(); *(float*)(msg + 9) = getMaxStamina(); *(float*)(msg + 13) = getStamina(); *(float*)(msg + 17) = getMaxHunger(); *(float*)(msg + 21) = getHunger(); *(float*)(msg + 25) = getMaxThirst(); *(float*)(msg + 29) = getThirst(); zResponse->setMessage(msg, 33); delete[] guiId; break; } case 1: // remove status bar observer { char len; zRequest->lese(&len, 1); char* guiId = new char[(int)len + 1]; zRequest->lese(guiId, len); guiId[(int)len] = 0; removeStatusBarObserver(zSource, guiId); delete[] guiId; break; } } } void Entity::onFall(float collisionSpeed) { if (collisionSpeed > 10) { setHP(getCurrentHP() - (collisionSpeed - 10.f) / 2.5f); } } void Entity::setChatSecurityLevel(int level) { chatSecurityLevel = level; } void Entity::setPosition(Framework::Vec3 pos) { location = pos; } void Entity::takeDamage(Entity* zSource, float damage) { // TODO: implement this } void Entity::setHP(float hp) { currentHP = MIN(MAX(hp, 0), maxHP); NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 1; *(float*)(message + 1) = getMaxHP(); *(float*)(message + 5) = getCurrentHP(); msg->setMessage(message, 9); notifyStatusBarObservers(msg); if (currentHP == 0) { onDeath(); } } void Entity::setStamina(float stamina) { this->stamina = MIN(MAX(stamina, 0), maxStamina); NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 2; *(float*)(message + 1) = getMaxStamina(); *(float*)(message + 5) = getStamina(); msg->setMessage(message, 9); notifyStatusBarObservers(msg); } void Entity::setHunger(float hunger) { this->hunger = MIN(MAX(hunger, 0), maxHunger); NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 3; *(float*)(message + 1) = getMaxHunger(); *(float*)(message + 5) = getHunger(); msg->setMessage(message, 9); notifyStatusBarObservers(msg); } void Entity::setThirst(float thirst) { this->thirst = MIN(MAX(thirst, 0), maxThirst); NetworkMessage* msg = new NetworkMessage(); char* message = new char[9]; message[0] = 4; *(float*)(message + 1) = getMaxThirst(); *(float*)(message + 5) = getThirst(); msg->setMessage(message, 9); notifyStatusBarObservers(msg); } void Entity::setGravityMultiplier(float multiplier) { gravityMultiplier = multiplier; } float Entity::getMaxHP() const { return maxHP; } float Entity::getCurrentHP() const { return currentHP; } float Entity::getStamina() const { return stamina; } float Entity::getMaxStamina() const { return maxStamina; } float Entity::getHunger() const { return hunger; } float Entity::getMaxHunger() const { return maxHunger; } float Entity::getThirst() const { return thirst; } float Entity::getMaxThirst() const { return maxThirst; } Framework::Vec3 Entity::getSpeed() const { return speed; } Framework::Vec3 Entity::getFaceDir() const { return faceDir; } Framework::Vec3 Entity::getPosition() const { return location; } float Entity::getGravityMultiplier() const { return gravityMultiplier; } float Entity::getJumpSpeed() const { return jumpSpeed; } void Entity::setJumpSpeed(float speed) { jumpSpeed = speed; } bool Entity::isRemoved() const { return removed; } const EntityType* Entity::zType() const { return Game::INSTANCE->zEntityType(typeId); } const ActionTarget* Entity::zTarget() const { return target; } int Entity::getId() const { return id; } bool Entity::hasDefaultModel() const { return 1; } ModelInfo* Entity::zSpecialModel() const { return 0; } float Entity::getMaxSpeed() const { return maxMovementSpeed; } bool Entity::isMoving() const { return movements.getEintragAnzahl() > 0; } int Entity::getChatSecurityLevel() const { return chatSecurityLevel; }