/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 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, see . * */ #include "twine/scene/actor.h" #include "common/textconsole.h" #include "twine/audio/sound.h" #include "twine/debugger/debug_state.h" #include "twine/parser/entity.h" #include "twine/renderer/renderer.h" #include "twine/renderer/screens.h" #include "twine/resources/hqr.h" #include "twine/resources/resources.h" #include "twine/scene/animations.h" #include "twine/scene/extra.h" #include "twine/scene/gamestate.h" #include "twine/scene/grid.h" #include "twine/scene/movements.h" #include "twine/scene/scene.h" #include "twine/shared.h" #include "twine/twine.h" namespace TwinE { Actor::Actor(TwinEEngine *engine) : _engine(engine) { } void Actor::restartPerso() { ActorStruct *sceneHero = _engine->_scene->_sceneHero; sceneHero->_move = ControlMode::kManual; memset(&sceneHero->_workFlags, 0, sizeof(sceneHero->_workFlags)); memset(&sceneHero->_flags, 0, sizeof(sceneHero->_flags)); sceneHero->_flags.bComputeCollisionWithObj = 1; sceneHero->_flags.bComputeCollisionWithBricks = 1; sceneHero->_flags.bCheckZone = 1; sceneHero->_flags.bCanDrown = 1; sceneHero->_flags.bObjFallable = 1; sceneHero->_armor = 1; sceneHero->_offsetTrack = -1; sceneHero->_labelTrack = -1; sceneHero->_offsetLife = 0; sceneHero->_zoneSce = -1; sceneHero->_beta = _previousHeroAngle; _engine->_movements->initRealAngle(sceneHero->_beta, sceneHero->_beta, LBAAngles::ANGLE_0, &sceneHero->realAngle); setBehaviour(_previousHeroBehaviour); _cropBottomScreen = 0; } void Actor::loadBehaviourEntity(ActorStruct *actor, EntityData &entityData, int16 &bodyAnimIndex, int32 index) { _engine->_resources->loadEntityData(entityData, index); actor->_entityDataPtr = &entityData; bodyAnimIndex = entityData.getAnimIndex(AnimationTypes::kStanding); if (bodyAnimIndex == -1) { error("Could not find animation data for 3d data with index %i", index); } } void Actor::loadHeroEntities() { ActorStruct *sceneHero = _engine->_scene->_sceneHero; loadBehaviourEntity(sceneHero, _heroEntityATHLETIC, _heroAnimIdxATHLETIC, FILE3DHQR_HEROATHLETIC); loadBehaviourEntity(sceneHero, _heroEntityAGGRESSIVE, _heroAnimIdxAGGRESSIVE, FILE3DHQR_HEROAGGRESSIVE); loadBehaviourEntity(sceneHero, _heroEntityDISCRETE, _heroAnimIdxDISCRETE, FILE3DHQR_HERODISCRETE); loadBehaviourEntity(sceneHero, _heroEntityPROTOPACK, _heroAnimIdxPROTOPACK, FILE3DHQR_HEROPROTOPACK); loadBehaviourEntity(sceneHero, _heroEntityNORMAL, _heroAnimIdxNORMAL, FILE3DHQR_HERONORMAL); _engine->_animations->_currentActorAnimExtraPtr = AnimationTypes::kStanding; sceneHero->_ptrAnimAction = _engine->_animations->_currentActorAnimExtraPtr; } void Actor::setBehaviour(HeroBehaviourType behaviour) { ActorStruct *sceneHero = _engine->_scene->_sceneHero; switch (behaviour) { case HeroBehaviourType::kNormal: _heroBehaviour = behaviour; sceneHero->_entityDataPtr = &_heroEntityNORMAL; break; case HeroBehaviourType::kAthletic: _heroBehaviour = behaviour; sceneHero->_entityDataPtr = &_heroEntityATHLETIC; break; case HeroBehaviourType::kAggressive: _heroBehaviour = behaviour; sceneHero->_entityDataPtr = &_heroEntityAGGRESSIVE; break; case HeroBehaviourType::kDiscrete: _heroBehaviour = behaviour; sceneHero->_entityDataPtr = &_heroEntityDISCRETE; break; case HeroBehaviourType::kProtoPack: _heroBehaviour = behaviour; sceneHero->_entityDataPtr = &_heroEntityPROTOPACK; break; case HeroBehaviourType::kMax: break; } const BodyType bodyIdx = sceneHero->_genBody; sceneHero->_body = -1; sceneHero->_genBody = BodyType::btNone; initBody(bodyIdx, OWN_ACTOR_SCENE_INDEX); sceneHero->_genAnim = AnimationTypes::kAnimNone; sceneHero->_flagAnim = AnimType::kAnimationTypeRepeat; _engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, OWN_ACTOR_SCENE_INDEX); } void Actor::setFrame(int32 actorIdx, uint32 frame) { #if 0 // TODO: converted from asm - not yet adapted ActorStruct *obj = _engine->_scene->getActor(actorIdx); T_PTR_NUM tempNextBody = obj->NextBody; void *tempNextTexture = obj->NextTexture; if (frame >= obj->NbFrames) { return; } obj->_body = tempNextBody; obj->Texture = tempNextTexture; T_PTR_NUM tempAnim = obj->_anim; void (*TransFctAnim)() = nullptr; // Couldn't find this yet if (TransFctAnim != nullptr) { uint32 ebp = frame; TransFctAnim(); tempAnim = (T_PTR_NUM)(void *)tempAnim.Ptr; frame = ebp; } obj->Interpolator = 0; obj->LastAnimStepX = 0; obj->LastAnimStepY = 0; obj->LastAnimStepZ = 0; uint16 nbGroups = *((uint16 *)(tempAnim.Ptr + 2)); obj->LastAnimStepAlpha = 0; obj->LastAnimStepBeta = 0; obj->LastAnimStepGamma = 0; obj->LastOfsIsPtr = 0; uint32 lastOfsFrame = nbGroups * 8 + 8; // infos frame + 4 WORDs per group obj->LastNbGroups = nbGroups; obj->NextNbGroups = nbGroups; obj->NbGroups = nbGroups; lastOfsFrame *= frame; uint32 timerRefHR = 0; // Replace with actual TimerRefHR lastOfsFrame += 8; // Skip header obj->LastTimer = timerRefHR; obj->Time = timerRefHR; obj->Status = 1; // STATUS_FRAME obj->LastOfsFrame = lastOfsFrame; obj->LastFrame = frame; uint32 ecx = nbGroups * 2 - 2; // 2 DWORDs per group, no group 0 T_PTR_NUM ebpPtr = tempAnim; tempAnim.Ptr = tempAnim.Ptr + lastOfsFrame + 16; memcpy(obj->CurrentFrame, tempAnim.Ptr, ecx); if (++frame == obj->NbFrames) { uint16 time = *((uint16 *)(ebpPtr.Ptr + 8)); frame = 0; tempAnim.Ptr = (void *)(8); } else { uint16 time = *((uint16 *)tempAnim.Ptr); tempAnim.Ptr -= ebpPtr.Ptr; tempAnim.Num += obj->LastTimer; obj->NextFrame = frame; obj->NextOfsFrame = (uint32)(tempAnim.Ptr); obj->NextTimer = time; obj->Master = *((uint16 *)(tempAnim.Ptr + 8)); obj->Status = 1; // STATUS_FRAME } #endif } void Actor::initSprite(int32 spriteNum, int32 actorIdx) { ActorStruct *localActor = _engine->_scene->getActor(actorIdx); localActor->_sprite = spriteNum; // lba2 if (!localActor->_flags.bSprite3D) { return; } if (spriteNum != -1 && localActor->_body != spriteNum) { const BoundingBox *spritebbox = _engine->_resources->_spriteBoundingBox.bbox(spriteNum); localActor->_body = spriteNum; localActor->_boundingBox = *spritebbox; } } TextId Actor::getTextIdForBehaviour() const { if (_heroBehaviour == HeroBehaviourType::kAggressive && _combatAuto) { return TextId::kBehaviourAggressiveAuto; } // the other values are matching the text ids return (TextId)(int32)_heroBehaviour; } int32 Actor::searchBody(BodyType bodyIdx, int32 actorIdx, ActorBoundingBox &actorBoundingBox) { if (bodyIdx == BodyType::btNone) { return -1; } ActorStruct *actor = _engine->_scene->getActor(actorIdx); const EntityBody *body = actor->_entityDataPtr->getEntityBody((int)bodyIdx); if (body == nullptr) { warning("Failed to get entity body for body idx %i", (int)bodyIdx); return -1; } actorBoundingBox = body->actorBoundingBox; return (int)bodyIdx; } void Actor::initBody(BodyType gennewbody, int16 actorIdx) { ActorStruct *localActor = _engine->_scene->getActor(actorIdx); if (localActor->_flags.bSprite3D) { return; } debug(1, "Load body %i for actor %i", (int)gennewbody, actorIdx); if (IS_HERO(actorIdx) && _heroBehaviour == HeroBehaviourType::kProtoPack && gennewbody != BodyType::btTunic && gennewbody != BodyType::btNormal) { setBehaviour(HeroBehaviourType::kNormal); } ActorBoundingBox actorBoundingBox; const int32 newBody = searchBody(gennewbody, actorIdx, actorBoundingBox); if (newBody == -1) { localActor->_genBody = BodyType::btNone; localActor->_body = -1; localActor->_boundingBox = BoundingBox(); debug("Failed to initialize body %i for actor %i", (int)gennewbody, actorIdx); return; } if (localActor->_body == newBody) { return; } const int32 oldBody = localActor->_body; localActor->_body = newBody; localActor->_genBody = gennewbody; if (actorBoundingBox.hasBoundingBox) { localActor->_boundingBox = actorBoundingBox.bbox; } else { const BodyData &bd = localActor->_entityDataPtr->getBody(localActor->_body); localActor->_boundingBox = bd.bbox; int32 size = 0; const int32 distX = bd.bbox.maxs.x - bd.bbox.mins.x; const int32 distZ = bd.bbox.maxs.z - bd.bbox.mins.z; if (localActor->_flags.bUseMiniZv) { // take smaller for bound if (distX < distZ) size = distX / 2; else size = distZ / 2; } else { // take average for bound size = (distZ + distX) / 4; } localActor->_boundingBox.mins.x = -size; localActor->_boundingBox.maxs.x = size; localActor->_boundingBox.mins.z = -size; localActor->_boundingBox.maxs.z = size; } if (oldBody != -1 && localActor->_anim != -1) { copyInterAnim(localActor->_entityDataPtr->getBody(oldBody), localActor->_entityDataPtr->getBody(localActor->_body)); } } void Actor::copyInterAnim(const BodyData &src, BodyData &dest) { if (!src.isAnimated() || !dest.isAnimated()) { return; } dest._animTimerData = src._animTimerData; const int16 numBones = MIN((int16)src.getNumBones(), (int16)dest.getNumBones()); for (int16 i = 0; i < numBones; ++i) { const BoneFrame *srcBoneFrame = src.getBoneState(i); BoneFrame *destBoneFrame = dest.getBoneState(i); *destBoneFrame = *srcBoneFrame; } } void Actor::startInitObj(int16 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); if (actor->_flags.bSprite3D) { if (actor->_hitForce != 0) { actor->_workFlags.bIsHitting = 1; } actor->_body = -1; initSprite(actor->_sprite, actorIdx); _engine->_movements->initRealAngle(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, &actor->realAngle); if (actor->_flags.bSpriteClip) { actor->_animStep = actor->posObj(); } } else { actor->_body = -1; debug(1, "Init actor %i with model %i", actorIdx, (int)actor->_genBody); initBody(actor->_genBody, actorIdx); actor->_anim = -1; actor->_flagAnim = AnimType::kAnimationTypeRepeat; if (actor->_body != -1) { _engine->_animations->initAnim(actor->_genAnim, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); } _engine->_movements->initRealAngle(actor->_beta, actor->_beta, LBAAngles::ANGLE_0, &actor->realAngle); } actor->_offsetTrack = -1; actor->_labelTrack = -1; actor->_offsetLife = 0; } void Actor::initObject(int16 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); *actor = ActorStruct(_engine->getMaxLife()); actor->_actorIdx = actorIdx; actor->_posObj = IVec3(0, SIZE_BRICK_Y, 0); memset(&actor->_flags, 0, sizeof(StaticFlagsStruct)); memset(&actor->_workFlags, 0, sizeof(DynamicFlagsStruct)); memset(&actor->_bonusParameter, 0, sizeof(BonusParameter)); _engine->_movements->initRealAngle(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, &actor->realAngle); } void Actor::hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 hitforce, int32 angle) { ActorStruct *actor = _engine->_scene->getActor(actorIdxAttacked); if (actor->_lifePoint <= 0) { return; } if (IS_HERO(actorIdxAttacked) && _engine->_debugState->_godMode) { return; } actor->_hitBy = actorIdx; debugC(1, TwinE::kDebugCollision, "Actor %d was hit by %d", actorIdxAttacked, actorIdx); if (actor->_armor <= hitforce) { if (actor->_genAnim == AnimationTypes::kBigHit || actor->_genAnim == AnimationTypes::kHit2) { if (actor->_nextGenAnim != AnimationTypes::kStanding) { const int32 tmpAnimPos = actor->_frame; actor->_frame = 1; _engine->_animations->processAnimActions(actorIdxAttacked); actor->_frame = tmpAnimPos; } } else { if (angle != -1) { _engine->_movements->initRealAngle(angle, angle, LBAAngles::ANGLE_0, &actor->realAngle); } if (_engine->getRandomNumber() & 1) { _engine->_animations->initAnim(AnimationTypes::kHit2, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked); } else { _engine->_animations->initAnim(AnimationTypes::kBigHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked); } } _engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars); if (IS_HERO(actorIdxAttacked)) { _engine->_movements->_lastJoyFlag = true; } // TODO: in the original sources this in an else block - dotemu release doesn't have this (so we are going after dotmeu here) // else { actor->_lifePoint -= hitforce; // } if (actor->_lifePoint < 0) { actor->_lifePoint = 0; } } else { _engine->_animations->initAnim(AnimationTypes::kHit, AnimType::kAnimationInsert, AnimationTypes::kNoAnim, actorIdxAttacked); } } void Actor::checkCarrier(int32 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); if (!actor->_flags.bIsCarrierActor) { return; } for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) { ActorStruct *otherActor = _engine->_scene->getActor(a); if (otherActor->_carryBy == actorIdx) { otherActor->_carryBy = -1; } } } void Actor::giveExtraBonus(int32 actorIdx) { ActorStruct *actor = _engine->_scene->getActor(actorIdx); const int bonusSprite = _engine->_extra->getBonusSprite(actor->_bonusParameter); if (bonusSprite == -1) { return; } if (actor->_workFlags.bIsDead) { _engine->_extra->addExtraBonus(actor->posObj(), LBAAngles::ANGLE_90, LBAAngles::ANGLE_0, bonusSprite, actor->_bonusAmount); _engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, actor->posObj(), actorIdx); } else { const ActorStruct *sceneHero = _engine->_scene->_sceneHero; const int32 angle = _engine->_movements->getAngle(actor->posObj(), sceneHero->posObj()); const IVec3 pos(actor->_posObj.x, actor->_posObj.y + actor->_boundingBox.maxs.y, actor->_posObj.z); _engine->_extra->addExtraBonus(pos, LBAAngles::ANGLE_70, angle, bonusSprite, actor->_bonusAmount); _engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, pos, actorIdx); } } // Lba2 #define START_AROUND_BETA 1024 #define END_AROUND_BETA 3072 #define STEP_AROUND_BETA 128 // 16 pos testees #define GetAngle2D(x0, z0, x1, z1) GetAngleVector2D((x1) - (x0), (z1) - (z0)) void Actor::posObjectAroundAnother(uint8 numsrc, uint8 numtopos) { #if 0 ActorStruct *objsrc; ActorStruct *objtopos; int32 beta, dist, dist2; int32 step; objsrc = _engine->_scene->getActor(numsrc); objtopos = _engine->_scene->getActor(numtopos); int32 xb = objsrc->Obj.X; int32 zb = objsrc->Obj.Z; objtopos->Obj.Y = objsrc->Obj.Y; dist = MAX(objsrc->XMin, objsrc->XMax); dist = MAX(dist, objsrc->ZMin); dist = MAX(dist, objsrc->ZMax); dist2 = MAX(objtopos->XMin, objtopos->XMax); dist2 = MAX(dist2, objtopos->ZMin); dist2 = MAX(dist2, objtopos->ZMax); dist += dist / 2 + dist2 + dist2 / 2; beta = ClampAngle(objsrc->Obj.Beta + START_AROUND_BETA); for (step = 0; step < (4096 / STEP_AROUND_BETA); step++, beta += STEP_AROUND_BETA) { beta &= 4095; _engine->_renderer->rotate(0, dist, beta); objtopos->Obj.X = xb + X0; objtopos->Obj.Z = zb + Z0; if (_engine->_collision->checkValidObjPos(numtopos, numsrc)) { // accepte position break; } } objtopos->Obj.Beta = ClampAngle(GetAngle2D(xb, zb, objtopos->Obj.X, objtopos->Obj.Z)); #endif } int16 RealValue::getRealValueFromTime(int32 time) { if (timeValue) { const int32 delta = time - memoTicks; if (delta >= timeValue) { // rotation is finished timeValue = 0; return endValue; } int32 t = ((endValue - startValue) * delta) / timeValue; t += startValue; return (int16)t; } return endValue; } int16 RealValue::getRealAngle(int32 time) { if (timeValue) { int32 delta = time - memoTicks; if (delta < timeValue) { int32 t = NormalizeAngle(endValue - startValue); t = (t * delta) / timeValue; t += startValue; return (int16)t; } timeValue = 0; } return endValue; } bool ActorStruct::isAttackAnimationActive() const { return _genAnim == AnimationTypes::kRightPunch || _genAnim == AnimationTypes::kLeftPunch || _genAnim == AnimationTypes::kKick; } bool ActorStruct::isAttackWeaponAnimationActive() const { return _genAnim == AnimationTypes::kSabreAttack || _genAnim == AnimationTypes::kThrowBall || _genAnim == AnimationTypes::kSabreUnknown; } bool ActorStruct::isJumpAnimationActive() const { return _genAnim == AnimationTypes::kJump; } } // namespace TwinE