Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,550 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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>((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

378
engines/twine/scene/actor.h Normal file
View File

@@ -0,0 +1,378 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_ACTOR_H
#define TWINE_SCENE_ACTOR_H
#include "common/scummsys.h"
#include "twine/parser/anim.h"
#include "twine/parser/body.h"
#include "twine/parser/entity.h"
#include "twine/shared.h"
namespace TwinE {
/** Total number of sprites allowed in the game */
#define NUM_SPRITES 425 // 200 for lba1
/** Total number of bodies allowed in the game */
#define NUM_BODIES 469 // 131 for lba1
/** Actors move structure */
struct RealValue {
int16 startValue = 0;
int16 endValue = 0;
int16 timeValue = 0;
int32 memoTicks = 0;
/**
* Get actor real angle
* @param time engine time used for interpolation
*/
int16 getRealValueFromTime(int32 time);
int16 getRealAngle(int32 time);
};
/** Actors static flags structure */
struct StaticFlagsStruct {
uint32 bComputeCollisionWithObj : 1; // 0x000001 CHECK_OBJ_COL
uint32 bComputeCollisionWithBricks : 1; // 0x000002 CHECK_BRICK_COL
uint32 bCheckZone : 1; // 0x000004 CHECK_ZONE - testing of scenaric areas
uint32 bSpriteClip : 1; // 0x000008 SPRITE_CLIP - (doors) fixed clip area
uint32 bCanBePushed : 1; // 0x000010 PUSHABLE
uint32 bComputeLowCollision : 1; // 0x000020 COL_BASSE
uint32 bCanDrown : 1; // 0x000040 CHECK_CODE_JEU
uint32 bComputeCollisionWithFloor : 1; // 0x000080 CHECK_WATER_COL
uint32 bUnk0100 : 1; // 0x000100
uint32 bIsInvisible : 1; // 0x000200 INVISIBLE - not drawn but all computed
uint32 bSprite3D : 1; // 0x000400 SPRITE_3D - a sprite not a 3D object
uint32 bObjFallable : 1; // 0x000800 OBJ_FALLABLE
uint32 bNoShadow : 1; // 0x001000 NO_SHADOW - no auto shadow
uint32 bIsBackgrounded : 1; // 0x002000 OBJ_BACKGROUND - is embedded in the decor the 1st time
uint32 bIsCarrierActor : 1; // 0x004000 OBJ_CARRIER - can carry and move an obj
// take smaller value for bound, or if not set take average for bound
uint32 bUseMiniZv : 1; // 0x008000 MINI_ZV - square on smaller dimension (if 3D object)
uint32 bHasInvalidPosition : 1; // 0x010000 POS_INVALIDE - carrier considered as an invalid position
uint32 bNoElectricShock : 1; // 0x020000 NO_CHOC - does not trigger electric shock animation
uint32 bHasSpriteAnim3D : 1; // 0x040000 ANIM_3DS - 3DS animation (extension of 3D sprite)
uint32 bNoPreClipping : 1; // 0x080000 NO_PRE_CLIP - does not pre-clip the object (for large objects)
uint32 bHasZBuffer : 1; // 0x100000 OBJ_ZBUFFER - displays object in ZBuffer (exterior only!)
uint32 bHasZBufferInWater : 1; // 0x200000 OBJ_IN_WATER - displays object in ZBuffer in water (exterior only!)
};
/** Actors dynamic flags structure */
struct DynamicFlagsStruct {
uint32 bWaitHitFrame : 1; // 0x0001 WAIT_HIT_FRAME - wait for hit frame
uint32 bIsHitting : 1; // 0x0002 OK_HIT - hit frame anim
uint32 bAnimEnded : 1; // 0x0004 ANIM_END - anim ended in the current loop (will be looped in the next engine loop)
uint32 bAnimNewFrame : 1; // 0x0008 NEW_FRAME - new frame anim reached
uint32 bWasDrawn : 1; // 0x0010 WAS_DRAWN - actor has been drawn in this loop
uint32 bIsDead : 1; // 0x0020 OBJ_DEAD - is dead
uint32 bIsSpriteMoving : 1; // 0x0040 AUTO_STOP_DOOR - door is opening or closing (wait to reach the destination position)
uint32 bIsRotationByAnim : 1; // 0x0080 ANIM_MASTER_ROT - actor rotation is managed by its animation not by the engine
uint32 bIsFalling : 1; // 0x0100 FALLING - is falling on scene
uint32 bIsTargetable : 1; // 0x0200 IS_TARGETABLE (lba1) OK_SUPER_HIT (lba2)
uint32 bIsBlinking : 1; // 0x0400 IS_BLINKING (lba1) FRAME_SHIELD (lba2)
uint32 bWasWalkingBeforeFalling : 1; // 0x0800 DRAW_SHADOW (lba2) - bWasWalkingBeforeFalling in lba1
uint32 bANIM_MASTER_GRAVITY : 1; // 0x1000 ANIM_MASTER_GRAVITY (lba2)
uint32 bSKATING : 1; // 0x2000 SKATING (lba2) Ouch! I slip in a forbidden collision
uint32 bOK_RENVOIE : 1; // 0x4000 OK_RENVOIE (lba2) ready to send back a projectile
uint32 bLEFT_JUMP : 1; // 0x8000 LEFT_JUMP (lba2) ready to jump from the left foot
uint32 bRIGHT_JUMP : 1; // RIGHT_JUMP (1<<16) // (lba2) ready to jump from the right foot
uint32 bWAIT_SUPER_HIT : 1; // WAIT_SUPER_HIT (1<<17) // (lba2) waiting for the end of the animation before giving another super hit
uint32 bTRACK_MASTER_ROT : 1; // TRACK_MASTER_ROT (1<<18) // (lba2) it's the track that manages the direction
uint32 bFLY_JETPACK : 1; // FLY_JETPACK (1<<19) // (lba2) flying with the Jetpack
uint32 bDONT_PICK_CODE_JEU : 1; // DONT_PICK_CODE_JEU (1<<20) // (lba2) Cheat - Conveyor Belt Zones
uint32 bMANUAL_INTER_FRAME : 1; // MANUAL_INTER_FRAME (1<<21) // (lba2) Manually performs the ObjectSetInterFrame()
uint32 bWAIT_COORD : 1; // WAIT_COORD (1<<22) // (lba2) waiting to have been displayed to pass the coordinates from one point to an extra
uint32 bCHECK_FALLING : 1; // CHECK_FALLING (1<<23) // (lba2) forces object to test FALLING during a frame
};
/**
* Bonus type flags - a bitfield value, of which the bits mean:
* bit 8: clover leaf,
* bit 7: small key,
* bit 6: magic,
* bit 5: life,
* bit 4: money,
* If more than one type of bonus is selected, the actual type of bonus
* will be chosen randomly each time player uses Action.
*/
struct BonusParameter {
uint16 givenNothing : 1;
uint16 unk2 : 1; // unused in lba1
uint16 unk3 : 1; // unused in lba1
uint16 unk4 : 1; // unused in lba1
uint16 kashes : 1;
uint16 lifepoints : 1;
uint16 magicpoints : 1;
uint16 key : 1;
uint16 cloverleaf : 1;
uint16 unused : 7;
};
/**
* Actors structure
*
* Such as characters, doors, moving platforms, invisible actors, ...
*/
class ActorStruct { // T_OBJET
private:
ShapeType _col = ShapeType::kNone; // collision
bool _brickCausesDamage = false;
int32 _maxLife;
public:
ActorStruct(int maxLife = 0) : _lifePoint(maxLife), _maxLife(maxLife) {}
StaticFlagsStruct _flags;
DynamicFlagsStruct _workFlags;
EntityData _entityData;
inline ShapeType brickShape() const { return _col; }
inline void setCollision(ShapeType shapeType) {
_col = shapeType;
_brickCausesDamage = false;
}
inline void setBrickCausesDamage() { _brickCausesDamage = true; }
inline bool brickCausesDamage() { return _brickCausesDamage; }
void addLife(int32 val);
void setLife(int32 val);
bool isAttackWeaponAnimationActive() const;
bool isAttackAnimationActive() const;
bool isJumpAnimationActive() const;
const IVec3 &posObj() const;
int32 _body = -1; // costumeIndex - index into bodyTable
BodyType _genBody = BodyType::btNormal;
BodyType _saveGenBody = BodyType::btNormal; // lba2
AnimationTypes _genAnim = AnimationTypes::kAnimNone;
AnimationTypes _nextGenAnim = AnimationTypes::kStanding;
AnimationTypes _ptrAnimAction = AnimationTypes::kAnimNone;
int32 _sprite = 0;
EntityData *_entityDataPtr = nullptr;
int16 _actorIdx = 0; // own actor index
IVec3 _posObj; // PosObjX, PosObjY, PosObjZ
// T_ANIM_3DS - Coord.A3DS
struct A3DSAnim {
int32 Num;
int32 Deb;
int32 Fin;
} A3DS;
int32 _hitForce = 0;
int32 _hitBy = -1;
BonusParameter _bonusParameter;
int32 _beta = 0; // facing angle of actor. Minumum is 0 (SW). Going counter clock wise
int32 _srot = 40; // speed of rotation
ControlMode _move = ControlMode::kNoMove; // Move
int32 _delayInMillis = 0; // Info
int32 _cropLeft = 0; // Info
int32 _cropTop = 0; // Info1
int32 _cropRight = 0; // Info2
int32 _cropBottom = 0; // Info3
int32 _followedActor = 0; // same as Info3
int32 _bonusAmount = 0;
int32 _talkColor = COLOR_BLACK;
int32 _armor = 1;
int32 _lifePoint = 0;
/** Process actor coordinate Nxw, Nyw, Nzw */
IVec3 _processActor;
IVec3 _oldPos; // OldPosX, OldPosY, OldPosZ
int32 _offsetTrack = -1;
uint8 *_ptrTrack = nullptr;
int32 _moveScriptSize = 0;
int32 _offsetLife = 0;
int32 _saveOffsetLife = 0; // lba2
uint8 *_lifeScript = nullptr;
int32 _lifeScriptSize = 0;
int32 _labelTrack = 0; // script label index
int32 _offsetLabelTrack = 0; // pointer to LABEL offset
int32 _memoLabelTrack = 0;
/**
* colliding actor id
*/
int32 _objCol = -1;
/**
* actor id we are standing on
*/
int32 _carryBy = -1;
int32 _zoneSce = -1;
int32 _animStepBeta = 0;
IVec3 _animStep;
int32 _anim = -1;
int32 _doorWidth = 0;
int32 _frame = 0;
AnimType _flagAnim = AnimType::kAnimationTypeRepeat;
int32 _spriteActorRotation = 0;
uint8 _brickSound = 0U; // CodeJeu
int32 SampleAlways = 0; // lba2
uint8 SampleVolume = 0; // lba2
// SizeSHit contains the number of the brick under the wagon - hack
int16 SizeSHit; // lba2 - always square
// T_OBJ_3D Obj; // lba2
// T_GROUP_INFO CurrentFrame[30]; // lba2
BoundingBox _boundingBox; // Xmin, YMin, Zmin, Xmax, Ymax, Zmax
RealValue realAngle;
};
inline const IVec3 &ActorStruct::posObj() const {
return _posObj;
}
inline void ActorStruct::addLife(int32 val) {
setLife(_lifePoint + val);
}
inline void ActorStruct::setLife(int32 val) {
_lifePoint = val;
if (_lifePoint > _maxLife) {
_lifePoint = _maxLife;
}
}
class TwinEEngine;
class Actor {
private:
TwinEEngine *_engine;
/** Hero 3D entity for normal behaviour */
EntityData _heroEntityNORMAL;
/** Hero 3D entity for athletic behaviour */
EntityData _heroEntityATHLETIC;
/** Hero 3D entity for aggressive behaviour */
EntityData _heroEntityAGGRESSIVE;
/** Hero 3D entity for discrete behaviour */
EntityData _heroEntityDISCRETE;
/** Hero 3D entity for protopack behaviour */
EntityData _heroEntityPROTOPACK;
/**
* Initialize 3D actor body
* @param bodyIdx 3D actor body index
* @param actorIdx 3D actor index
*/
int32 searchBody(BodyType bodyIdx, int32 actorIdx, ActorBoundingBox &actorBoundingBox);
void loadBehaviourEntity(ActorStruct *actor, EntityData &entityData, int16 &bodyAnimIndex, int32 index);
void copyInterAnim(const BodyData &src, BodyData &dest);
public:
Actor(TwinEEngine *engine);
HeroBehaviourType _heroBehaviour = HeroBehaviourType::kNormal; // Comportement
HeroBehaviourType _saveHeroBehaviour = HeroBehaviourType::kNormal; // SaveComportementHero (lba2)
/** Hero auto aggressive mode */
bool _combatAuto = true;
/** Previous Hero behaviour */
HeroBehaviourType _previousHeroBehaviour = HeroBehaviourType::kNormal;
/** Previous Hero angle */
int16 _previousHeroAngle = 0;
int16 _cropBottomScreen = 0; // TODO: usage differ in original sources
/** Hero current anim for normal behaviour */
int16 _heroAnimIdxNORMAL = 0;
/** Hero current anim for athletic behaviour */
int16 _heroAnimIdxATHLETIC = 0;
/** Hero current anim for aggressive behaviour */
int16 _heroAnimIdxAGGRESSIVE = 0;
/** Hero current anim for discrete behaviour */
int16 _heroAnimIdxDISCRETE = 0;
/** Hero current anim for protopack behaviour */
int16 _heroAnimIdxPROTOPACK = 0;
/** Hero anim for behaviour menu */
int16 _heroAnimIdx[4];
void initSprite(int32 spriteNum, int32 actorIdx);
void setFrame(int32 actorIdx, uint32 frame);
/** Restart hero variables while opening new scenes */
void restartPerso();
/** Load hero 3D body and animations */
void loadHeroEntities();
TextId getTextIdForBehaviour() const;
/**
* Set hero behaviour
* @param behaviour behaviour value to set
*/
void setBehaviour(HeroBehaviourType behaviour); // SetComportement
/**
* Initialize 3D actor
* @param bodyIdx 3D actor body index
* @param actorIdx 3D actor index
*/
void initBody(BodyType bodyIdx, int16 actorIdx);
/**
* Initialize actors
* @param actorIdx actor index to init
*/
void startInitObj(int16 actorIdx);
/**
* Reset actor
* @param actorIdx actor index to init
*/
void initObject(int16 actorIdx);
/**
* Process hit actor
* @param actorIdx actor hitting index
* @param actorIdxAttacked actor attacked index
* @param strengthOfHit actor hitting strength of hit
* @param angle angle of actor hitting
*/
void hitObj(int32 actorIdx, int32 actorIdxAttacked, int32 strengthOfHit, int32 angle);
/** Process actor carrier */
void checkCarrier(int32 actorIdx);
/** Process actor extra bonus */
void giveExtraBonus(int32 actorIdx);
// Lba2
void posObjectAroundAnother(uint8 numsrc, uint8 numtopos); // PosObjetAroundAnother
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,848 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/animations.h"
#include "common/util.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/parser/anim.h"
#include "twine/parser/entity.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/collision.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"
namespace TwinE {
static const int32 magicLevelStrengthOfHit[] = {
MagicballStrengthType::kNoBallStrength,
MagicballStrengthType::kYellowBallStrength,
MagicballStrengthType::kGreenBallStrength,
MagicballStrengthType::kRedBallStrength,
MagicballStrengthType::kFireBallStrength,
0};
Animations::Animations(TwinEEngine *engine) : _engine(engine) {
}
int32 Animations::searchAnim(AnimationTypes animIdx, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const int32 bodyAnimIndex = actor->_entityDataPtr->getAnimIndex(animIdx);
if (bodyAnimIndex != -1) {
_currentActorAnimExtraPtr = animIdx;
}
return bodyAnimIndex;
}
int16 Animations::patchInterAngle(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const {
const int16 lastAngle = ClampAngle(lastAngle1);
const int16 nextAngle = ClampAngle(newAngle1);
int16 angleDiff = nextAngle - lastAngle;
int16 computedAngle;
if (angleDiff) {
if (angleDiff < -LBAAngles::ANGLE_180) {
angleDiff += LBAAngles::ANGLE_360;
} else if (angleDiff > LBAAngles::ANGLE_180) {
angleDiff -= LBAAngles::ANGLE_360;
}
computedAngle = lastAngle + (angleDiff * deltaTime) / keyFrameLength;
} else {
computedAngle = lastAngle;
}
return ClampAngle(computedAngle);
}
int16 Animations::patchInterStep(int32 deltaTime, int32 keyFrameLength, int16 newPos, int16 lastPos) const {
int16 distance = newPos - lastPos;
int16 computedPos;
if (distance) {
computedPos = lastPos + (distance * deltaTime) / keyFrameLength;
} else {
computedPos = lastPos;
}
return computedPos;
}
bool Animations::doSetInterAnimObjet(int32 framedest, const AnimData &animData, BodyData &pBody, AnimTimerDataStruct *ptranimdest, bool global) {
if (!pBody.isAnimated()) {
return false;
}
const KeyFrame *keyFrame = animData.getKeyframe(framedest);
const int16 numBones = pBody.getNumBones();
int32 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
const int32 timeDest = keyFrame->length;
const KeyFrame *lastKeyFramePtr = ptranimdest->ptr;
int32 remainingFrameTime = ptranimdest->time;
if (lastKeyFramePtr == nullptr) {
lastKeyFramePtr = keyFrame;
remainingFrameTime = timeDest;
}
const int32 time = _engine->timerRef - remainingFrameTime;
if (time >= timeDest) {
ptranimdest->ptr = keyFrame;
if (global) {
ptranimdest->time = _engine->timerRef;
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepAlpha = ToAngle(keyFrame->animStepAlpha);
_animStepBeta = ToAngle(keyFrame->animStepBeta);
_animStepGamma = ToAngle(keyFrame->animStepGamma);
}
copyKeyFrameToState(keyFrame, pBody, numOfBonesInAnim);
return true;
}
if (global) {
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepAlpha = (keyFrame->animStepAlpha * time) / timeDest;
_animStepBeta = (keyFrame->animStepBeta * time) / timeDest;
_animStepGamma = (keyFrame->animStepGamma * time) / timeDest;
}
if (numOfBonesInAnim <= 1) {
return false;
}
int16 boneIdx = 1;
int16 tmpNumOfPoints = MIN<int16>(lastKeyFramePtr->boneframes.size() - 1, numOfBonesInAnim - 1);
do {
BoneFrame *boneState = pBody.getBoneState(boneIdx);
const BoneFrame &boneFrame = keyFrame->boneframes[boneIdx];
const BoneFrame &lastBoneFrame = lastKeyFramePtr->boneframes[boneIdx];
boneState->type = boneFrame.type;
switch (boneFrame.type) {
case BoneType::TYPE_ROTATE:
boneState->x = patchInterAngle(time, timeDest, boneFrame.x, lastBoneFrame.x);
boneState->y = patchInterAngle(time, timeDest, boneFrame.y, lastBoneFrame.y);
boneState->z = patchInterAngle(time, timeDest, boneFrame.z, lastBoneFrame.z);
break;
case BoneType::TYPE_TRANSLATE:
case BoneType::TYPE_ZOOM:
boneState->x = patchInterStep(time, timeDest, boneFrame.x, lastBoneFrame.x);
boneState->y = patchInterStep(time, timeDest, boneFrame.y, lastBoneFrame.y);
boneState->z = patchInterStep(time, timeDest, boneFrame.z, lastBoneFrame.z);
break;
default:
error("Unsupported animation rotation mode %d", boneFrame.type);
}
++boneIdx;
} while (--tmpNumOfPoints);
return false;
}
void Animations::setAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
const int32 numOfKeyframeInAnim = animData.getKeyframes().size();
if (keyframeIdx < 0 || keyframeIdx >= numOfKeyframeInAnim) {
return;
}
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animMasterRot = keyFrame->animMasterRot;
_animStepBeta = ToAngle(keyFrame->animStepBeta);
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->timerRef;
const int16 numBones = bodyData.getNumBones();
int16 numOfBonesInAnim = animData.getNumBoneframes();
if (numOfBonesInAnim > numBones) {
numOfBonesInAnim = numBones;
}
copyKeyFrameToState(keyFrame, bodyData, numOfBonesInAnim);
}
void Animations::stockInterAnim(const BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
if (!bodyData.isAnimated()) {
return;
}
if (_animKeyframeBufIdx >= ARRAYSIZE(_animKeyframeBuf)) {
_animKeyframeBufIdx = 0;
}
animTimerDataPtr->time = _engine->timerRef;
KeyFrame *keyframe = &_animKeyframeBuf[_animKeyframeBufIdx++];
animTimerDataPtr->ptr = keyframe;
copyStateToKeyFrame(keyframe, bodyData);
}
void Animations::copyStateToKeyFrame(KeyFrame *keyframe, const BodyData &bodyData) const {
const int32 numBones = bodyData.getNumBones();
keyframe->boneframes.clear();
keyframe->boneframes.reserve(numBones);
for (int32 i = 0; i < numBones; ++i) {
const BoneFrame *boneState = bodyData.getBoneState(i);
keyframe->boneframes.push_back(*boneState);
}
}
void Animations::copyKeyFrameToState(const KeyFrame *keyframe, BodyData &bodyData, int32 numBones) const {
for (int32 i = 0; i < numBones; ++i) {
BoneFrame *boneState = bodyData.getBoneState(i);
*boneState = keyframe->boneframes[i];
}
}
bool Animations::setInterDepObjet(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr) {
const KeyFrame *keyFrame = animData.getKeyframe(keyframeIdx);
const int32 timeDest = keyFrame->length;
int32 remainingFrameTime = animTimerDataPtr->time;
if (animTimerDataPtr->ptr == nullptr) {
remainingFrameTime = timeDest;
}
const int32 time = _engine->timerRef - remainingFrameTime;
_animMasterRot = keyFrame->animMasterRot;
if (time >= timeDest) {
_animStep.x = keyFrame->x;
_animStep.y = keyFrame->y;
_animStep.z = keyFrame->z;
_animStepAlpha = ToAngle(keyFrame->animStepAlpha);
_animStepBeta = ToAngle(keyFrame->animStepBeta);
_animStepGamma = ToAngle(keyFrame->animStepGamma);
animTimerDataPtr->ptr = animData.getKeyframe(keyframeIdx);
animTimerDataPtr->time = _engine->timerRef;
return true; // finished animation
}
_animStep.x = (keyFrame->x * time) / timeDest;
_animStep.y = (keyFrame->y * time) / timeDest;
_animStep.z = (keyFrame->z * time) / timeDest;
_animStepAlpha = ToAngle((keyFrame->animStepAlpha * time) / timeDest);
_animStepBeta = ToAngle((keyFrame->animStepBeta * time) / timeDest);
_animStepGamma = ToAngle((keyFrame->animStepGamma * time) / timeDest);
return false;
}
void Animations::processAnimActions(int32 actorIdx) { // GereAnimAction
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_entityDataPtr == nullptr || actor->_ptrAnimAction == AnimationTypes::kAnimNone) {
return;
}
const Common::Array<EntityAnim::Action> *actions = actor->_entityDataPtr->getActions(actor->_ptrAnimAction);
if (actions == nullptr) {
return;
}
for (const EntityAnim::Action &action : *actions) {
debugC(1, TwinE::kDebugAnimation, "Execute animation action %d for actor %d", (int)action.type, actorIdx);
switch (action.type) {
case ActionType::ACTION_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_hitForce = action.strength;
actor->_workFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_SAMPLE:
if (action.animFrame == actor->_frame) {
_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_SAMPLE_FREQ:
if (action.animFrame == actor->_frame) {
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(action.frequency) - (action.frequency / 2);
_engine->_sound->mixSample3D(action.sampleIndex, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_EXTRA_BONUS:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_MAGIC_BALL:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
_engine->_extra->addExtraThrowMagicball(actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle);
}
break;
case ActionType::ACTION_SAMPLE_REPEAT:
if (action.animFrame == actor->_frame) {
_engine->_sound->mixSample3D(action.sampleIndex, 0x1000, action.repeat, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_THROW_SEARCH:
if (action.animFrame == actor->_frame) {
_engine->_extra->addExtraAiming(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_ALPHA:
if (action.animFrame == actor->_frame) {
_engine->_extra->throwExtra(actorIdx, actor->_posObj.x, actor->_posObj.y + action.yHeight, actor->_posObj.z, action.spriteIndex, action.xAngle, actor->_beta + action.yAngle, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_SAMPLE_STOP:
if (action.animFrame == actor->_frame) {
_engine->_sound->stopSample(action.sampleIndex);
}
break;
case ActionType::ACTION_LEFT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorBegin;
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_RIGHT_STEP:
if (action.animFrame == actor->_frame && (actor->_brickSound & 0xF0U) != 0xF0U) {
const int16 sampleIdx = (actor->_brickSound & 0x0FU) + Samples::WalkFloorRightBegin;
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(1000) - 500;
_engine->_sound->mixSample3D(sampleIdx, pitchBend, 1, actor->posObj(), actorIdx);
}
break;
case ActionType::ACTION_HERO_HITTING:
if (action.animFrame - 1 == actor->_frame) {
actor->_hitForce = magicLevelStrengthOfHit[_engine->_gameState->_magicLevelIdx];
actor->_workFlags.bIsHitting = 1;
}
break;
case ActionType::ACTION_THROW_3D:
if (action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_posObj.x;
const int32 throwY = action.distanceY + actor->_posObj.y;
const int32 throwZ = destPos.y + actor->_posObj.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_ALPHA:
if (action.animFrame == actor->_frame) {
const int32 distance = getDistance2D(actor->posObj(), _engine->_scene->_sceneHero->posObj());
const int32 newAngle = _engine->_movements->getAngle(actor->_posObj.y, 0, _engine->_scene->_sceneHero->_posObj.y, distance);
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 throwX = destPos.x + actor->_posObj.x;
const int32 throwY = action.distanceY + actor->_posObj.y;
const int32 throwZ = destPos.y + actor->_posObj.z;
_engine->_extra->throwExtra(actorIdx, throwX, throwY, throwZ, action.spriteIndex,
action.xAngle + newAngle, action.yAngle + actor->_beta, action.xRotPoint, action.extraAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_SEARCH:
if (action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_posObj.x + destPos.x;
const int32 y = actor->_posObj.y + action.distanceY;
const int32 z = actor->_posObj.z + destPos.y;
_engine->_extra->addExtraAiming(actorIdx, x, y, z, action.spriteIndex,
action.targetActor, action.finalAngle, action.strength);
}
break;
case ActionType::ACTION_THROW_3D_MAGIC:
if (_engine->_gameState->_magicBall == -1 && action.animFrame == actor->_frame) {
const IVec2 &destPos = _engine->_renderer->rotate(action.distanceX, action.distanceZ, actor->_beta);
const int32 x = actor->_posObj.x + destPos.x;
const int32 y = actor->_posObj.y + action.distanceY;
const int32 z = actor->_posObj.z + destPos.y;
_engine->_extra->addExtraThrowMagicball(x, y, z, action.xAngle, actor->_beta, action.yAngle, action.finalAngle);
}
break;
case ActionType::ACTION_ZV:
default:
break;
}
}
}
bool Animations::initAnim(AnimationTypes genNewAnim, AnimType flag, AnimationTypes genNextAnim, int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return false;
}
if (actor->_flags.bSprite3D) {
return false;
}
if (genNewAnim == actor->_genAnim && actor->_anim != -1) {
return true;
}
if (genNextAnim == AnimationTypes::kNoAnim && actor->_flagAnim != AnimType::kAnimationAllThen) {
genNextAnim = actor->_genAnim;
}
int32 newanim = searchAnim(genNewAnim, actorIdx);
if (newanim == -1) {
newanim = searchAnim(AnimationTypes::kStanding, actorIdx);
if (newanim == -1) {
error("Could not find anim index for 'standing' (actor %i)", actorIdx);
}
}
if (flag != AnimType::kAnimationSet && actor->_flagAnim == AnimType::kAnimationAllThen) {
actor->_nextGenAnim = genNewAnim;
return false;
}
if (flag == AnimType::kAnimationInsert) {
flag = AnimType::kAnimationAllThen;
genNextAnim = actor->_genAnim;
if (genNextAnim == AnimationTypes::kThrowBall || genNextAnim == AnimationTypes::kFall || genNextAnim == AnimationTypes::kLanding || genNextAnim == AnimationTypes::kLandingHit) {
genNextAnim = AnimationTypes::kStanding;
}
}
if (flag == AnimType::kAnimationSet) {
flag = AnimType::kAnimationAllThen;
}
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
if (actor->_anim == -1) {
// if no previous animation
setAnimObjet(0, _engine->_resources->_animData[newanim], bodyData, &bodyData._animTimerData);
} else {
// interpolation between animations
stockInterAnim(bodyData, &bodyData._animTimerData);
}
actor->_anim = newanim;
actor->_genAnim = genNewAnim;
actor->_nextGenAnim = genNextAnim;
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = flag;
actor->_frame = 0;
actor->_workFlags.bIsHitting = 0;
actor->_workFlags.bAnimEnded = 0;
actor->_workFlags.bAnimNewFrame = 1;
processAnimActions(actorIdx);
actor->_animStepBeta = LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
debugC(1, TwinE::kDebugAnimation, "Change animation for actor %d to %d", actorIdx, newanim);
return true;
}
void Animations::doAnim(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return;
}
const IVec3 &oldPos = actor->_oldPos;
IVec3 &processActor = actor->_processActor;
if (actor->_flags.bSprite3D) {
if (actor->_hitForce) {
actor->_workFlags.bIsHitting = 1;
}
processActor = actor->posObj();
if (!actor->_workFlags.bIsFalling) {
if (actor->_srot) {
int32 xAxisRotation = actor->realAngle.getRealValueFromTime(_engine->timerRef);
if (!xAxisRotation) {
if (actor->realAngle.endValue > 0) {
xAxisRotation = 1;
} else {
xAxisRotation = -1;
}
}
const IVec2 xRotPos = _engine->_renderer->rotate(xAxisRotation, 0, actor->_spriteActorRotation);
processActor.y = actor->_posObj.y - xRotPos.y;
const IVec2 destPos = _engine->_renderer->rotate(0, xRotPos.x, actor->_beta);
processActor.x = actor->_posObj.x + destPos.x;
processActor.z = actor->_posObj.z + destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, actor->_srot, LBAAngles::ANGLE_17, &actor->realAngle);
if (actor->_workFlags.bIsSpriteMoving) {
if (actor->_doorWidth) { // open door
if (getDistance2D(processActor.x, processActor.z, actor->_animStep.x, actor->_animStep.z) >= actor->_doorWidth) {
if (actor->_beta == LBAAngles::ANGLE_0) { // down
processActor.z = actor->_animStep.z + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_90) { // right
processActor.x = actor->_animStep.x + actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_180) { // up
processActor.z = actor->_animStep.z - actor->_doorWidth;
} else if (actor->_beta == LBAAngles::ANGLE_270) { // left
processActor.x = actor->_animStep.x - actor->_doorWidth;
}
actor->_workFlags.bIsSpriteMoving = 0;
actor->_srot = 0;
}
} else { // close door
bool updatePos = false;
if (actor->_beta == LBAAngles::ANGLE_0) { // down
if (processActor.z <= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_90) { // right
if (processActor.x <= actor->_animStep.x) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_180) { // up
if (processActor.z >= actor->_animStep.z) {
updatePos = true;
}
} else if (actor->_beta == LBAAngles::ANGLE_270) { // left
if (processActor.x >= actor->_animStep.x) {
updatePos = true;
}
}
if (updatePos) {
processActor = actor->_animStep;
actor->_workFlags.bIsSpriteMoving = 0;
actor->_srot = 0;
}
}
}
}
if (actor->_flags.bCanBePushed) {
processActor += actor->_animStep;
if (actor->_flags.bUseMiniZv) {
processActor.x = ((processActor.x / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
processActor.z = ((processActor.z / (SIZE_BRICK_XZ / 4)) * (SIZE_BRICK_XZ / 4));
}
actor->_animStep = IVec3();
}
}
} else { // 3D actor
if (actor->_anim != -1) {
const AnimData &animData = _engine->_resources->_animData[actor->_anim];
bool keyFramePassed = false;
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
if (bodyData.isAnimated()) {
keyFramePassed = setInterDepObjet(actor->_frame, animData, &bodyData._animTimerData);
}
if (_animMasterRot) {
actor->_workFlags.bIsRotationByAnim = 1;
} else {
actor->_workFlags.bIsRotationByAnim = 0;
}
actor->_beta = ClampAngle(actor->_beta + _animStepBeta - actor->_animStepBeta);
actor->_animStepBeta = _animStepBeta;
const IVec2 &destPos = _engine->_renderer->rotate(_animStep.x, _animStep.z, actor->_beta);
_animStep.x = destPos.x;
_animStep.z = destPos.y;
processActor = actor->posObj() + _animStep - actor->_animStep;
actor->_animStep = _animStep;
actor->_workFlags.bAnimEnded = 0;
actor->_workFlags.bAnimNewFrame = 0;
if (keyFramePassed) {
actor->_frame++;
actor->_workFlags.bAnimNewFrame = 1;
// if actor have animation actions to process
processAnimActions(actorIdx);
int16 numKeyframe = actor->_frame;
if (numKeyframe == (int16)animData.getNbFramesAnim()) {
actor->_workFlags.bIsHitting = 0;
if (actor->_flagAnim == AnimType::kAnimationTypeRepeat) {
actor->_frame = animData.getLoopFrame();
} else {
actor->_genAnim = actor->_nextGenAnim;
actor->_anim = searchAnim(actor->_genAnim, actorIdx);
if (actor->_anim == -1) {
actor->_anim = searchAnim(AnimationTypes::kStanding, actorIdx);
actor->_genAnim = AnimationTypes::kStanding;
}
actor->_ptrAnimAction = _currentActorAnimExtraPtr;
actor->_flagAnim = AnimType::kAnimationTypeRepeat;
actor->_frame = 0;
actor->_hitForce = 0;
}
processAnimActions(actorIdx);
actor->_workFlags.bAnimEnded = 1;
}
actor->_animStepBeta = LBAAngles::ANGLE_0;
actor->_animStep = IVec3();
}
}
}
Collision* collision = _engine->_collision;
// actor standing on another actor
if (actor->_carryBy != -1) {
const ActorStruct *standOnActor = _engine->_scene->getActor(actor->_carryBy);
processActor -= standOnActor->_oldPos;
processActor += standOnActor->posObj();
if (!collision->checkZvOnZv(actorIdx, actor->_carryBy)) {
actor->_carryBy = -1; // no longer standing on other actor
}
}
// actor falling Y speed
if (actor->_workFlags.bIsFalling) {
processActor = oldPos;
processActor.y += _engine->_stepFalling; // add step to fall
}
// actor collisions with bricks
uint32 col1 = 0; /** Cause damage in current processed actor */
if (actor->_flags.bComputeCollisionWithBricks) {
ShapeType col = _engine->_grid->worldColBrick(oldPos);
if (col != ShapeType::kNone) {
if (col == ShapeType::kSolid) {
processActor.y = (processActor.y / SIZE_BRICK_Y) * SIZE_BRICK_Y + SIZE_BRICK_Y; // go upper
actor->_posObj.y = processActor.y;
} else {
collision->reajustPos(processActor, col);
}
}
if (actor->_flags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
if (actor->_carryBy != -1 && actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->setCollisionPos(processActor);
if (IS_HERO(actorIdx) && !actor->_flags.bComputeLowCollision) {
// check hero collisions with bricks
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
col1 |= collision->doCornerReajustTwinkel(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
} else {
// check other actors collisions with bricks
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 1);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.mins.z, 2);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.maxs.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 4);
col1 |= collision->doCornerReajust(actor, actor->_boundingBox.mins.x, actor->_boundingBox.mins.y, actor->_boundingBox.maxs.z, 8);
}
// process wall hit while running
if (col1 && !actor->_workFlags.bIsFalling && IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward) {
IVec2 destPos = _engine->_renderer->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::ANGLE_315 + LBAAngles::ANGLE_180);
destPos.x += processActor.x;
destPos.y += processActor.z;
if (destPos.x >= 0 && destPos.y >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.y <= SCENE_SIZE_MAX) {
if (_engine->_grid->worldColBrick(destPos.x, processActor.y + SIZE_BRICK_Y, destPos.y) != ShapeType::kNone && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
if (IS_HERO(actorIdx)) {
_engine->_movements->_lastJoyFlag = true;
}
actor->addLife(-1);
}
}
}
col = _engine->_grid->worldColBrick(processActor);
actor->setCollision(col);
if (col != ShapeType::kNone) {
if (col == ShapeType::kSolid) {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
processActor.y = (collision->_collision.y * SIZE_BRICK_Y) + SIZE_BRICK_Y;
} else {
if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour == HeroBehaviourType::kAthletic && actor->_genAnim == AnimationTypes::kForward && _engine->_cfgfile.WallCollision) { // avoid wall hit damage
_engine->_extra->initSpecial(actor->_posObj.x, actor->_posObj.y + 1000, actor->_posObj.z, ExtraSpecialType::kHitStars);
initAnim(AnimationTypes::kBigHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
_engine->_movements->_lastJoyFlag = true;
actor->addLife(-1);
}
// no Z coordinate issue
if (_engine->_grid->worldColBrick(processActor.x, processActor.y, oldPos.z) != ShapeType::kNone) {
if (_engine->_grid->worldColBrick(oldPos.x, processActor.y, processActor.z) != ShapeType::kNone) {
return;
} else {
processActor.x = oldPos.x;
}
} else {
processActor.z = oldPos.z;
}
}
} else {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, col);
}
if (actor->_workFlags.bIsFalling) {
debugC(1, TwinE::kDebugCollision, "Actor %d reset falling", actorIdx);
}
actor->_workFlags.bIsFalling = 0;
} else {
if (actor->_flags.bObjFallable && actor->_carryBy == -1) {
col = _engine->_grid->worldColBrick(processActor.x, processActor.y - 1, processActor.z);
if (col != ShapeType::kNone) {
if (actor->_workFlags.bIsFalling) {
collision->receptionObj(actorIdx);
}
collision->reajustPos(processActor, col);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
debugC(1, TwinE::kDebugCollision, "Actor %d is falling", actorIdx);
actor->_workFlags.bIsFalling = 1;
if (IS_HERO(actorIdx) && _engine->_scene->_startYFalling == 0) {
_engine->_scene->_startYFalling = processActor.y;
int32 y = processActor.y - 1 - SIZE_BRICK_Y;
while (y > 0 && ShapeType::kNone == _engine->_grid->worldColBrick(processActor.x, y, processActor.z)) {
y -= SIZE_BRICK_Y;
}
y = (y + SIZE_BRICK_Y) & ~(SIZE_BRICK_Y - 1);
int32 fallHeight = processActor.y - y;
if (fallHeight <= (2 * SIZE_BRICK_Y) && actor->_genAnim == AnimationTypes::kForward) {
actor->_workFlags.bWasWalkingBeforeFalling = 1;
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
} else {
initAnim(AnimationTypes::kFall, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
}
}
}
}
// if under the map, than die
if (collision->_collision.y == -1) {
actor->setLife(0);
}
} else {
if (actor->_flags.bComputeCollisionWithObj) {
collision->checkObjCol(actorIdx);
}
}
if (col1) {
actor->setBrickCausesDamage();
}
// check and fix actor bounding position
if (processActor.x < 0) {
processActor.x = 0;
}
if (processActor.y < 0) {
processActor.y = 0;
}
if (processActor.z < 0) {
processActor.z = 0;
}
if (processActor.x > SCENE_SIZE_MAX) {
processActor.x = SCENE_SIZE_MAX;
}
if (processActor.z > SCENE_SIZE_MAX) {
processActor.z = SCENE_SIZE_MAX;
}
actor->_posObj = processActor;
}
} // namespace TwinE

View File

@@ -0,0 +1,133 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_ANIMATIONS_H
#define TWINE_SCENE_ANIMATIONS_H
#include "common/scummsys.h"
#include "twine/parser/anim.h"
namespace TwinE {
struct AnimTimerDataStruct;
class BodyData;
class TwinEEngine;
class Animations {
private:
TwinEEngine *_engine;
int16 patchInterAngle(int32 deltaTime, int32 keyFrameLength, int16 newAngle1, int16 lastAngle1) const;
int16 patchInterStep(int32 deltaTime, int32 keyFrameLength, int16 newPos, int16 lastPos) const;
/**
* Verify animation at keyframe
* @param keyframeIdx Animation key frame index
* @param animData Animation data
* @param animTimerDataPtr Animation time data
*/
bool setInterDepObjet(int32 keyframeIdx, const AnimData &animData, AnimTimerDataStruct *animTimerDataPtr);
void copyKeyFrameToState(const KeyFrame *keyframe, BodyData &bodyData, int32 numBones) const;
void copyStateToKeyFrame(KeyFrame *keyframe, const BodyData &bodyData) const;
bool doSetInterAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPt, bool global);
int _animKeyframeBufIdx = 0;
KeyFrame _animKeyframeBuf[32];
/** Rotation by anim and not by engine */
int16 _animMasterRot = 0;
/** Last rotation angle */
int16 _animStepBeta = 0;
int16 _animStepAlpha = 0;
int16 _animStepGamma = 0;
/** Current step coordinates */
IVec3 _animStep;
public:
Animations(TwinEEngine *engine);
/** Current actor anim extra pointer */
AnimationTypes _currentActorAnimExtraPtr = AnimationTypes::kAnimNone;
/**
* Set animation keyframe
* @param keyframIdx Animation keyframe index
* @param animData Animation data
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
void setAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr);
/**
* Set new body animation
* @param keyframeIdx Animation key frame index
* @param animData Animation data
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
bool setInterAnimObjet(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
return doSetInterAnimObjet(keyframeIdx, animData, bodyData, animTimerDataPtr, true);
}
bool setInterAnimObjet2(int32 keyframeIdx, const AnimData &animData, BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr) {
return doSetInterAnimObjet(keyframeIdx, animData, bodyData, animTimerDataPtr, false);
}
/**
* Get entity anim index (This is taken from File3D entities)
* @param animIdx Entity animation index
* @param actorIdx Actor index
*/
int32 searchAnim(AnimationTypes animIdx, int32 actorIdx = OWN_ACTOR_SCENE_INDEX);
/**
* Stock animation - copy the next keyFrame from a different buffer
* @param bodyData Body model data
* @param animTimerDataPtr Animation time data
*/
void stockInterAnim(const BodyData &bodyData, AnimTimerDataStruct *animTimerDataPtr);
/**
* Initialize animation
* @param newAnim animation to init
* @param animType animation type
* @param animExtra animation actions extra data
* @param actorIdx actor index
*/
bool initAnim(AnimationTypes newAnim, AnimType animType, AnimationTypes animExtra, int32 actorIdx); // InitAnim
/**
* Process acotr animation actions
* @param actorIdx Actor index
*/
void processAnimActions(int32 actorIdx);
/**
* Process main loop actor animations
* @param actorIdx Actor index
*/
void doAnim(int32 actorIdx);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,709 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/buggy.h"
#include "twine/audio/sound.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
#define MAX_SAMPLE_PITCH 11000
#define MIN_SAMPLE_PITCH2 5000
#define MAX_SAMPLE_PITCH2 8500
#define MAX_SPEED 3800
#define TEMPO_GEAR 1200 // speed change
#define SAMPLE_BUGGY 109
namespace TwinE {
void Buggy::initBuggy(uint8 numobj, uint32 flaginit) {
S_BUGGY *ptb = &ListBuggy[0];
ActorStruct *ptrobj = _engine->_scene->getActor(numobj);
// So that the objects follow their tracks without being interrupted
// by the buggy (too bad, it will be pushed)
ptrobj->_flags.bCanBePushed = true;
ptrobj->_flags.bCanDrown = true;
if (flaginit == 2 // we force the repositioning of the buggy
|| (flaginit && !NumBuggy)) // first initialization
// because the empty buggy cannot be Twinsen
{
ptb->Cube = _engine->_scene->_numCube; // Port-Ludo (Desert)
ptb->X = ptrobj->_posObj.x;
ptb->Y = ptrobj->_posObj.y;
ptb->Z = ptrobj->_posObj.z;
ptb->Beta = ptrobj->_beta;
_engine->_actor->initBody(BodyType::btNormal, numobj);
NumBuggy = (uint8)(numobj | BUGGY_PRESENT);
} else if (NumBuggy) {
if (_engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_move != ControlMode::kBuggyManual && _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_move != ControlMode::kBuggy) {
int32 x, y;
if (_engine->_scene->_numCube == ptb->Cube) {
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
_engine->_actor->initBody(BodyType::btNormal, numobj);
} else if (_engine->_scene->loadSceneCubeXY(ptb->Cube, &x, &y)) {
x -= _engine->_scene->_currentCubeX;
y -= _engine->_scene->_currentCubeY;
ptrobj->_posObj.x = ptb->X + x * 32768;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z + y * 32768;
ptrobj->_beta = ptb->Beta;
ptrobj->_flags.bNoShadow = 1;
ptrobj->_flags.bIsBackgrounded = 1;
ptrobj->_flags.bNoElectricShock = 1;
ptrobj->_flags.bHasZBuffer = 1;
_engine->_actor->initBody(BodyType::btNormal, numobj);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
_engine->_movements->clearRealAngle(ptrobj);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
NumBuggy = (uint8)(numobj | BUGGY_PRESENT);
} else {
_engine->_actor->initBody(BodyType::btNone, numobj);
}
}
void Buggy::resetBuggy() {
S_BUGGY *ptb = &ListBuggy[0];
NumBuggy = 0;
ptb->Cube = 0;
}
void Buggy::takeBuggy() {
int32 sample;
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
S_BUGGY *ptb = &ListBuggy[0];
ptb->SpeedRot = 1024;
// TODO: ptb->LastTimer = TimerRefHR;
// TODO: ObjectClear(&ptrobj);
// Shielding in case the Buggy moved (being pushed, for example).
ptb->X = _engine->_scene->getActor(NUM_BUGGY)->_posObj.x;
ptb->Y = _engine->_scene->getActor(NUM_BUGGY)->_posObj.y;
ptb->Z = _engine->_scene->getActor(NUM_BUGGY)->_posObj.z;
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
_engine->_movements->clearRealAngle(ptrobj); // To avoid crushing the beta.
ptrobj->_workFlags.bMANUAL_INTER_FRAME = true;
ptrobj->_flags.bHasZBuffer = true;
// TODO: _engine->_actor->setBehaviour(HeroBehaviourType::kBUGGY);
// Switch Buggy Scenario to NoBody.
_engine->_actor->initBody(BodyType::btNone, NUM_BUGGY);
if (ptrobj->SampleAlways) {
_engine->_sound->stopSample(ptrobj->SampleAlways);
ptrobj->SampleAlways = 0;
}
sample = SAMPLE_BUGGY;
if (_engine->_sound->isSamplePlaying(sample)) {
_engine->_sound->stopSample(sample);
}
ptrobj->SampleVolume = 20;
// TODO: ParmSampleVolume = ptrobj->SampleVolume;
Gear = 0;
TimerGear = 0;
// TODO: ptrobj->SampleAlways = _engine->_sound->playSample(SAMPLE_BUGGY, 4096, 0, 0,
// ptrobj->_posObj.x, ptrobj->_posObj.y, ptrobj->_posObj.z);
}
#if 0
static void ObjectClear(T_OBJ *ptb3d) {
memset(ptb3d, 0, sizeof(T_OBJ));
ptb3d.OBJ_3D.Body, -1
ptb3d.OBJ_3D.NextBody, -1
ptb3d.OBJ_3D.Texture, -1
ptb3d.OBJ_3D.NextTexture, -1
ptb3d.OBJ_3D.Anim, -1
}
#endif
void Buggy::leaveBuggy(HeroBehaviourType behaviour) {
int32 sample;
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
S_BUGGY *ptb = &ListBuggy[0];
sample = SAMPLE_BUGGY;
if (_engine->_sound->isSamplePlaying(sample)) {
_engine->_sound->stopSample(sample);
ptrobj->SampleAlways = 0;
}
ptb->X = ptrobj->_posObj.x;
ptb->Y = ptrobj->_posObj.y;
ptb->Z = ptrobj->_posObj.z;
ptb->Beta = ptrobj->_beta;
ptb->Cube = _engine->_scene->_numCube;
// TODO: ObjectClear(ptrobj);
ptrobj->_workFlags.bMANUAL_INTER_FRAME = 0;
ptrobj->_flags.bHasZBuffer = 0;
_engine->_actor->initBody(BodyType::btTunic, OWN_ACTOR_SCENE_INDEX);
_engine->_actor->setBehaviour(behaviour);
// Restore scenario buggy.
ptrobj = _engine->_scene->getActor(NUM_BUGGY);
ptrobj->_posObj.x = ptb->X;
ptrobj->_posObj.y = ptb->Y;
ptrobj->_posObj.z = ptb->Z;
ptrobj->_beta = ptb->Beta;
ptrobj->_brickSound = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX)->_brickSound;
_engine->_movements->clearRealAngle(ptrobj); // To avoid crushing the beta
_engine->_actor->initBody(BodyType::btNormal, NUM_BUGGY);
// Search for a free position for Twinsen nearby.
_engine->_actor->posObjectAroundAnother(NUM_BUGGY, OWN_ACTOR_SCENE_INDEX);
}
#if 0
struct T_HALF_POLY {
uint32 Bank : 4; // coul bank poly
uint32 TexFlag : 2; // flag texture 00 rien 01 triste 10 flat 11 gouraud
uint32 PolyFlag : 2; // flag poly 00 rien 01 flat 10 gouraud 11 dither
uint32 SampleStep : 4; // sample pas twinsen
uint32 CodeJeu : 4; // code jeu
uint32 Sens : 1; // sens diagonale
uint32 Col : 1;
uint32 Dummy : 1;
uint32 IndexTex : 13; // index texture 8192
}; // 1 long
struct T_HALF_TEX {
uint16 Tx0;
uint16 Ty0;
uint16 Tx1;
uint16 Ty1;
uint16 Tx2;
uint16 Ty2;
}; // 2 Longs
int32 CalculAltitudeObjet(int32 x, int32 z, int32 cj) {
int32 y0, y1, y2, y3;
int32 dx, dz;
int32 dz0, dz1;
dx = x >> 9; // div512
dz = z >> 9; // div512
if ((dx < 0) || (dx > 63))
return -1;
if ((dz < 0) || (dz > 63))
return -1;
x &= 511;
z &= 511;
dz0 = dz * 65;
dz1 = dz0 + 65;
//---------------------------------------------------------------
if (cj == -1) {
T_HALF_POLY *mappoly = &MapPolyGround[dz * 64 * 2 + dx * 2];
if (mappoly->Sens == 0) // poly séparé par ligne reliant point 0 et 2
{
if (x >= z) // poly de droite
{
mappoly++;
}
} else // poly séparé par ligne reliant point 1 et 3
{
if (511 - x <= z) // poly de droite
{
mappoly++;
}
}
cj = (uint8)mappoly->CodeJeu;
}
//--------------------------------------------------------------
y0 = MapSommetY[dz0 + dx];
y1 = MapSommetY[dz1 + dx];
y2 = MapSommetY[dz1 + (dx + 1)];
y3 = MapSommetY[dz0 + (dx + 1)];
if (cj == CJ_FOOT_WATER OR cj == CJ_WATER) {
uint8 *i = &MapIntensity[dz0 + dx];
y0 += (i[0] >> 4) * -200;
y1 += (i[65] >> 4) * -200;
y2 += (i[65 + 1] >> 4) * -200;
y3 += (i[1] >> 4) * -200;
}
if (MapPolyGround[dz * 64 * 2 + dx * 2].Sens == 0) // poly séparé par ligne reliant point 0 et 2
{
if (x < z) // poly de gauche
{
return (y0 + ((y1 - y0) * z + (y2 - y1) * x) / 512);
} else // poly de droite
{
return (y0 + ((y3 - y0) * x + (y2 - y3) * z) / 512);
}
} else // poly séparé par ligne reliant point 1 et 3
{
if (511 - x > z) // poly de gauche
{
return (y0 + ((y3 - y0) * x + (y1 - y0) * z) / 512);
} else // poly de droite
{
return (y1 + ((y2 - y1) * x + (y3 - y2) * (511 - z)) / 512);
}
}
}
#endif
void Buggy::doAnimBuggy(ActorStruct *ptrobj) {
#if 0
int32 x1, y1, z1, yw;
S_BUGGY *ptb = &ListBuggy[0];
T_OBJ_3D *ptb3d = &ptrobj->Obj;
// wheels rot
int32 c, d;
int32 x, y, z;
// Trick to avoid crushing the groups in AffOneObject().
ObjectSetInterFrame(ptb3d);
if (ptrobj->_workFlags.bIsFalling || ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
return;
}
LongRotate(0, ptb->SpeedInc * 1024, ptb3d->Beta);
ptrobj->_processActor.x = ptb3d->X + X0 / 1024;
ptrobj->_processActor.z = ptb3d->Z + Z0 / 1024;
// Ideal altitude
yw = CalculAltitudeObjet(ptrobj->_processActor.x, ptrobj->_processActor.z, -1); // Ground Y for XZ
// test altitude #2: Forbidden triangles
// Front left wheel direction
ptb3d->CurrentFrame[3].Beta = (int16)ptb->BetaWheel;
// Front right wheel direction
ptb3d->CurrentFrame[6].Beta = (int16)ptb->BetaWheel;
// Management of 4 separate wheels.
// front right wheel
LongRotate(-400, 400, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (260 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[0].X, ptb->Wheel[0].Y, ptb->Wheel[0].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[0].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[0].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[0].X = x;
ptb->Wheel[0].Y = y;
ptb->Wheel[0].Z = z;
// front left wheel
LongRotate(400, 400, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (260 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[1].X, ptb->Wheel[1].Y, ptb->Wheel[1].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[1].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[1].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[1].X = x;
ptb->Wheel[1].Y = y;
ptb->Wheel[1].Z = z;
// back left wheel
LongRotate(400, -350, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (360 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[2].X, ptb->Wheel[2].Y, ptb->Wheel[2].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[2].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[2].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[2].X = x;
ptb->Wheel[2].Y = y;
ptb->Wheel[2].Z = z;
// back right wheel
LongRotate(-400, -350, ptb3d->Beta);
x = ptrobj->_processActor.x + X0;
z = ptrobj->_processActor.z + Z0;
y = yw;
if (x >= 0 && x < 32768 && z >= 0 && z < 32768) {
y += CalculAltitudeObjet(x, z, -1);
}
c = (360 * 31415) / 1000; // Circumference * 10
d = Distance3D(ptb->Wheel[3].X, ptb->Wheel[3].Y, ptb->Wheel[3].Z, x, y, z);
if (ptb->Speed >= 0) {
ptb->Wheel[3].Angle += (4096 * 10 * d) / c;
} else {
ptb->Wheel[3].Angle -= (4096 * 10 * d) / c;
}
ptb->Wheel[3].X = x;
ptb->Wheel[3].Y = y;
ptb->Wheel[3].Z = z;
// front right wheel
ptb3d->CurrentFrame[4].Alpha = (int16)ptb->Wheel[1].Angle;
// front left wheel
ptb3d->CurrentFrame[7].Alpha = (int16)ptb->Wheel[0].Angle;
// back left wheel
ptb3d->CurrentFrame[11].Alpha = (int16)ptb->Wheel[2].Angle;
// back right wheel
ptb3d->CurrentFrame[9].Alpha = (int16)ptb->Wheel[3].Angle;
// Car inclination (pitch)
ptb3d->CurrentFrame[1].Type = 0;
LongRotate(0, 400, ptb3d->Beta);
x1 = X0;
z1 = Z0;
LongRotate(0, -400, ptb3d->Beta);
if (Nxw + x1 >= 0 && Nxw + x1 < 32768 && Nzw + z1 >= 0 && Nzw + z1 < 32768) {
y = CalculAltitudeObjet(Nxw + x1, Nzw + z1, -1);
} else {
y = yw;
}
if (Nxw + X0 >= 0 && Nxw + X0 < 32768 && Nzw + Z0 >= 0 && Nzw + Z0 < 32768) {
y1 = CalculAltitudeObjet(Nxw + X0, Nzw + Z0, -1);
} else {
y1 = yw;
}
ptb3d->CurrentFrame[1].Alpha = (int16)(1024 - GetAngle2D(0, y, 800, y1));
ptb->Alpha = ptb3d->CurrentFrame[1].Alpha;
// Car inclination (roll)
LongRotate(400, 0, ptb3d->Beta);
x1 = X0;
z1 = Z0;
LongRotate(-400, 0, ptb3d->Beta);
if (Nxw + X0 >= 0 && Nxw + X0 < 32768 && Nzw + Z0 >= 0 && Nzw + Z0 < 32768) {
y = CalculAltitudeObjet(Nxw + X0, Nzw + Z0, -1);
} else {
y = yw;
}
if (Nxw + x1 >= 0 && Nxw + x1 < 32768 && Nzw + z1 >= 0 && Nzw + z1 < 32768) {
y1 = CalculAltitudeObjet(Nxw + x1, Nzw + z1, -1);
} else {
y1 = yw;
}
ptb3d->CurrentFrame[1].Gamma = (int16)GetAngle2D(y, 0, y1, 800);
// Steering wheel
ptb3d->CurrentFrame[12].Gamma = (int16)-ptb->BetaWheel;
// Twinsen's head
ptb3d->CurrentFrame[14].Beta = (int16)ptb->BetaWheel;
#endif
}
void Buggy::moveBuggy(ActorStruct *ptrobj) {
#if 0
S_BUGGY *ptb = &ListBuggy[0];
T_OBJ_3D *ptb3d = &ptrobj->Obj;
int32 pitch = 0;
int32 flagattack = false;
int32 speedinc;
int32 rotlevel;
int32 timerhr, deltatimer;
timerhr = TimerRefHR;
deltatimer = timerhr - ptb->LastTimer;
if ((Input & I_THROW) && (PtrComportement->Flags & CF_WEAPON)) {
// Are we in mage?
if (TabInv[FLAG_TUNIQUE].IdObj3D == 0) {
_engine->_actor->initBody(BodyType::btTunicTir, OWN_ACTOR_SCENE_INDEX);
} else {
_engine->_actor->initBody(BodyType::btMageTir, OWN_ACTOR_SCENE_INDEX);
}
_engine->_animations->initAnim(AnimationTypes::kThrowBall, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
/* control direction pendant Aiming */
if (!ptrobj->_workFlags.bIsRotationByAnim) {
ptb3d->Beta += GetDeltaMove(&ptrobj->BoundAngle.Move);
ptb3d->Beta &= 4095;
_engine->_movements->initRealAngleConst(ptrobj);
}
_engine->_movements->_lastJoyFlag = true;
flagattack = true;
} else {
if (LastInput & I_THROW) {
// We finished shooting with the buggy,
// we close the hood
_engine->_actor->initBody(BodyType::btTunic, OWN_ACTOR_SCENE_INDEX);
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
}
}
if (!flagattack && !ptrobj->_workFlags.bIsFalling && !ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
_engine->_movements->clearRealAngle(ptrobj);
if (_engine->_movements->_lastJoyFlag && (((Input & I_JOY) != LastMyJoy) || ((Input & I_FIRE) != LastMyFire))) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
Pushing = false;
}
_engine->_movements->_lastJoyFlag = false;
// Pushing contains the number of the object being pushed
// So 1000 is an impossible value used as an initialization flag
// no animation
if (Pushing == 1000) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
Pushing = false;
}
if (Input & I_UP) {
if (Pushing) {
_engine->_animations->initAnim(AnimationTypes::kPush, ANIM_TEMPO, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
} else {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
}
} else if (Input & I_DOWN) {
_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, OWN_ACTOR_SCENE_INDEX);
_engine->_movements->_lastJoyFlag = true;
}
}
if (!ptrobj->_workFlags.bIsFalling && !ptrobj->_workFlags.bANIM_MASTER_GRAVITY) {
// check speed command
if ((Input & I_UP) // accelerating
&& !flagattack) {
ptb->Speed += deltatimer * 4;
if (!TimerGear)
TimerGear = TimerRefHR + TEMPO_GEAR;
else {
if (Gear < 0)
Gear = 0;
if (TimerRefHR > TimerGear && Gear < 2) {
Gear++;
TimerGear = TimerRefHR + TEMPO_GEAR;
}
}
} else if ((Input & I_DOWN) // brake / reverse
&& !flagattack) {
ptb->Speed -= deltatimer * 12;
Gear = -1;
TimerGear = 0;
} else // slow down
{
if (ptb->Speed > 0) {
ptb->Speed -= deltatimer * 7;
if (ptb->Speed < 0) {
ptb->Speed = 0;
}
}
if (ptb->Speed < 0) {
ptb->Speed += deltatimer * 7;
if (ptb->Speed > 0) {
ptb->Speed = 0;
}
}
Gear = 0;
TimerGear = 0;
}
if (ptb->Speed < -2000)
ptb->Speed = -2000;
if (ptb->Speed > MAX_SPEED)
ptb->Speed = MAX_SPEED;
speedinc = ptb->Speed * deltatimer / 1000;
} else {
speedinc = 0;
}
// check dir
if (!flagattack) {
if (Input & I_RIGHT) {
ptb->BetaWheel = -300;
if (ptb->Speed) {
rotlevel = -ptb->SpeedRot * speedinc / ptb->Speed;
} else {
rotlevel = 0;
}
} else if (Input & I_LEFT) {
ptb->BetaWheel = 300;
if (ptb->Speed) {
rotlevel = ptb->SpeedRot * speedinc / ptb->Speed;
} else {
rotlevel = 0;
}
} else {
ptb->BetaWheel = 0;
rotlevel = 0;
}
if (ptrobj->_staticFlags.bSKATING) {
ptb->Speed = 3000;
speedinc = ptb->Speed * deltatimer / 1000;
} else {
if (ptb->Speed >= 0) {
ptb3d->Beta += rotlevel;
} else {
ptb3d->Beta -= rotlevel;
}
ptb3d->Beta = ClampAngle(ptb3d->Beta);
}
} else {
ptb->BetaWheel = 0;
}
LastMyJoy = Input & I_JOY;
LastMyFire = Input & I_FIRE;
LastInput = Input;
ptb->LastTimer = timerhr;
ptb->SpeedInc = speedinc;
if (ptrobj->SampleAlways && _engine->_sound->isSamplePlaying(ptrobj->SampleAlways)) {
int32 pitch;
switch (Gear) {
case -1:
pitch = boundRuleThree(3000, MAX_SAMPLE_PITCH2, MAX_SPEED, ABS(ptb->Speed));
break;
case 0:
pitch = boundRuleThree(3000, MAX_SAMPLE_PITCH, MAX_SPEED, ABS(ptb->Speed));
if (pitch >= MAX_SAMPLE_PITCH)
TimerGear = 1;
break;
case 1:
pitch = boundRuleThree(MAX_SAMPLE_PITCH2, MIN_SAMPLE_PITCH2, TEMPO_GEAR, TimerGear - TimerRefHR);
break;
default:
pitch = MAX_SAMPLE_PITCH2;
}
_engine->_sound->ChangePitchbendSample(ptrobj->SampleAlways, pitch);
}
#endif
}
} // namespace TwinE

View File

@@ -0,0 +1,81 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_BUGGY_H
#define TWINE_SCENE_BUGGY_H
#include "common/scummsys.h"
#include "twine/input.h"
#include "twine/scene/actor.h"
#define BUGGY_PRESENT 0x80
#define NUM_BUGGY ((uint8)(NumBuggy & ~(BUGGY_PRESENT)))
#define IsBuggyPresent() (NumBuggy & BUGGY_PRESENT)
namespace TwinE {
class Buggy {
private:
TwinEEngine *_engine;
int32 Gear = 0;
int32 TimerGear;
public:
#define MAX_BUGGYS 2
struct S_ONE_WHEEL {
int32 Angle = 0;
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
};
struct S_BUGGY {
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
int32 Cube = 0;
int32 Beta = 0;
int32 Alpha = 0;
int32 Gamma = 0;
S_ONE_WHEEL Wheel[4];
int32 BetaWheel = 0;
int32 SpeedInc = 0;
int32 SpeedRot = 0;
int32 Speed = 0;
int32 LastTimer = 0;
};
// TODO: rename and hide
S_BUGGY ListBuggy[MAX_BUGGYS];
uint8 NumBuggy;
Buggy(TwinEEngine *engine) : _engine(engine) {}
void initBuggy(uint8 numobj, uint32 flaginit);
void resetBuggy();
void takeBuggy();
void leaveBuggy(HeroBehaviourType behaviour);
void doAnimBuggy(ActorStruct *ptrobj);
void moveBuggy(ActorStruct *ptrobj);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,524 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/collision.h"
#include "common/util.h"
#include "twine/debugger/debug_state.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/extra.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 {
Collision::Collision(TwinEEngine *engine) : _engine(engine) {
}
bool Collision::checkZvOnZv(int32 actorIdx1, int32 actorIdx2) const {
const ActorStruct *actor1 = _engine->_scene->getActor(actorIdx1);
const ActorStruct *actor2 = _engine->_scene->getActor(actorIdx2);
const IVec3 &processActor = actor1->_processActor;
const IVec3 &mins1 = processActor + actor1->_boundingBox.mins;
const IVec3 &maxs1 = processActor + actor1->_boundingBox.maxs;
const IVec3 &mins2 = actor2->posObj() + actor2->_boundingBox.mins;
const IVec3 &maxs2 = actor2->posObj() + actor2->_boundingBox.maxs;
if (mins1.x >= maxs2.x) {
return false;
}
if (maxs1.x <= mins2.x) {
return false;
}
if (mins1.y > (maxs2.y + 1)) {
return false;
}
if (mins1.y <= (maxs2.y - SIZE_BRICK_Y)) {
return false;
}
if (maxs1.y <= mins2.y) {
return false;
}
if (mins1.z >= maxs2.z) {
return false;
}
if (maxs1.z <= mins2.z) {
return false;
}
return true;
}
void Collision::reajustPos(IVec3 &processActor, ShapeType brickShape) const {
if (brickShape <= ShapeType::kSolid) {
return;
}
const int32 xw = (_collision.x * SIZE_BRICK_XZ) - DEMI_BRICK_XZ; // upper left corner of the brick
const int32 yw = _collision.y * SIZE_BRICK_Y;
const int32 zw = (_collision.z * SIZE_BRICK_XZ) - DEMI_BRICK_XZ;
// double-side stairs
switch (brickShape) {
case ShapeType::kDoubleSideStairsTop1:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsTopRight;
} else {
brickShape = ShapeType::kStairsTopLeft;
}
break;
case ShapeType::kDoubleSideStairsBottom1:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsBottomRight;
} else {
brickShape = ShapeType::kStairsBottomLeft;
}
break;
case ShapeType::kDoubleSideStairsTop2:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsTopLeft;
} else {
brickShape = ShapeType::kStairsTopRight;
}
break;
case ShapeType::kDoubleSideStairsBottom2:
if (processActor.x - xw < processActor.z - zw) {
brickShape = ShapeType::kStairsBottomLeft;
} else {
brickShape = ShapeType::kStairsBottomRight;
}
break;
case ShapeType::kDoubleSideStairsLeft1:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsBottomLeft;
} else {
brickShape = ShapeType::kStairsTopLeft;
}
break;
case ShapeType::kDoubleSideStairsRight1:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsBottomRight;
} else {
brickShape = ShapeType::kStairsTopRight;
}
break;
case ShapeType::kDoubleSideStairsLeft2:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsTopLeft;
} else {
brickShape = ShapeType::kStairsBottomLeft;
}
break;
case ShapeType::kDoubleSideStairsRight2:
if (SIZE_BRICK_XZ - (processActor.x - xw) > processActor.z - zw) {
brickShape = ShapeType::kStairsTopRight;
} else {
brickShape = ShapeType::kStairsBottomRight;
}
break;
default:
break;
}
switch (brickShape) {
case ShapeType::kStairsTopLeft:
processActor.y = yw + boundRuleThree(0, SIZE_BRICK_Y, SIZE_BRICK_XZ, processActor.x - xw);
break;
case ShapeType::kStairsTopRight:
processActor.y = yw + boundRuleThree(0, SIZE_BRICK_Y, SIZE_BRICK_XZ, processActor.z - zw);
break;
case ShapeType::kStairsBottomLeft:
processActor.y = yw + boundRuleThree(SIZE_BRICK_Y, 0, SIZE_BRICK_XZ, processActor.z - zw);
break;
case ShapeType::kStairsBottomRight:
processActor.y = yw + boundRuleThree(SIZE_BRICK_Y, 0, SIZE_BRICK_XZ, processActor.x - xw);
break;
default:
break;
}
}
void Collision::handlePushing(IVec3 &processActor, const IVec3 &minsTest, const IVec3 &maxsTest, ActorStruct *ptrobj, ActorStruct *ptrobjt) {
const int32 newAngle = _engine->_movements->getAngle(processActor, ptrobjt->posObj());
// protect against chain reactions
if (ptrobjt->_flags.bCanBePushed && !ptrobj->_flags.bCanBePushed) {
ptrobjt->_animStep.y = 0;
if (ptrobjt->_flags.bUseMiniZv) {
if (newAngle >= LBAAngles::ANGLE_45 && newAngle < LBAAngles::ANGLE_135 && ptrobj->_beta >= LBAAngles::ANGLE_45 && ptrobj->_beta < LBAAngles::ANGLE_135) {
ptrobjt->_animStep.x = SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if (newAngle >= LBAAngles::ANGLE_135 && newAngle < LBAAngles::ANGLE_225 && ptrobj->_beta >= LBAAngles::ANGLE_135 && ptrobj->_beta < LBAAngles::ANGLE_225) {
ptrobjt->_animStep.z = -SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if (newAngle >= LBAAngles::ANGLE_225 && newAngle < LBAAngles::ANGLE_315 && ptrobj->_beta >= LBAAngles::ANGLE_225 && ptrobj->_beta < LBAAngles::ANGLE_315) {
ptrobjt->_animStep.x = -SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
if ((newAngle >= LBAAngles::ANGLE_315 || newAngle < LBAAngles::ANGLE_45) && (ptrobj->_beta >= LBAAngles::ANGLE_315 || ptrobj->_beta < LBAAngles::ANGLE_45)) {
ptrobjt->_animStep.z = SIZE_BRICK_XZ / 4 + SIZE_BRICK_XZ / 8;
}
} else {
// induced displacement before readjustment?
ptrobjt->_animStep.x = processActor.x - ptrobj->_oldPos.x;
ptrobjt->_animStep.z = processActor.z - ptrobj->_oldPos.z;
}
}
// so patch tempo
if ((ptrobjt->_boundingBox.maxs.x - ptrobjt->_boundingBox.mins.x == ptrobjt->_boundingBox.maxs.z - ptrobjt->_boundingBox.mins.z) &&
(ptrobj->_boundingBox.maxs.x - ptrobj->_boundingBox.mins.x == ptrobj->_boundingBox.maxs.z - ptrobj->_boundingBox.mins.z)) {
if (newAngle >= LBAAngles::ANGLE_45 && newAngle < LBAAngles::ANGLE_135) {
processActor.x = minsTest.x - ptrobj->_boundingBox.maxs.x;
}
if (newAngle >= LBAAngles::ANGLE_135 && newAngle < LBAAngles::ANGLE_225) {
processActor.z = maxsTest.z - ptrobj->_boundingBox.mins.z;
}
if (newAngle >= LBAAngles::ANGLE_225 && newAngle < LBAAngles::ANGLE_315) {
processActor.x = maxsTest.x - ptrobj->_boundingBox.mins.x;
}
if (newAngle >= LBAAngles::ANGLE_315 || newAngle < LBAAngles::ANGLE_45) {
processActor.z = minsTest.z - ptrobj->_boundingBox.maxs.z;
}
} else if (!ptrobj->_workFlags.bIsFalling) {
// refuse pos
processActor = ptrobj->_oldPos;
}
}
bool Collision::checkValidObjPos(int32 actorIdx) {
const ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
const IVec3 m0 = ptrobj->posObj() + ptrobj->_boundingBox.mins;
const IVec3 m1 = ptrobj->posObj() + ptrobj->_boundingBox.maxs;
if (m0.x < 0 || m0.x > SCENE_SIZE_MAX) {
return false;
}
if (m1.x < 0 || m1.x > SCENE_SIZE_MAX) {
return false;
}
if (m0.z < 0 || m0.z > SCENE_SIZE_MAX) {
return false;
}
if (m1.z < 0 || m1.z > SCENE_SIZE_MAX) {
return false;
}
Grid *grid = _engine->_grid;
if (grid->worldColBrickFull(m0.x, m0.y, m0.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m1.x, m0.y, m0.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m1.x, m0.y, m1.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
if (grid->worldColBrickFull(m0.x, m0.y, m1.z, ptrobj->_boundingBox.maxs.y, actorIdx) != ShapeType::kNone) {
return false;
}
for (int32 n = 0; n < _engine->_scene->_nbObjets; ++n) {
const ActorStruct *ptrobjt = _engine->_scene->getActor(n);
if (n != actorIdx && ptrobjt->_body != -1 && !ptrobj->_flags.bIsInvisible && ptrobjt->_carryBy != actorIdx) {
const IVec3 &t0 = ptrobjt->posObj() + ptrobjt->_boundingBox.mins;
const IVec3 &t1 = ptrobjt->posObj() + ptrobjt->_boundingBox.maxs;
if (m0.x < t1.x && m1.x > t0.x && m0.y < t1.y && m1.y > t0.y && m0.z < t1.z && m1.z > t0.z) {
return false;
}
}
}
return true;
}
int32 Collision::checkObjCol(int32 actorIdx) {
ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
IVec3 &processActor = ptrobj->_processActor;
IVec3 mins = processActor + ptrobj->_boundingBox.mins;
IVec3 maxs = processActor + ptrobj->_boundingBox.maxs;
int32 oldObjCol = ptrobj->_objCol;
ptrobj->_objCol = -1;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
ActorStruct *ptrobjt = _engine->_scene->getActor(a);
// avoid current processed actor
if (a != actorIdx && ptrobjt->_body != -1 && !ptrobj->_flags.bIsInvisible && ptrobjt->_carryBy != actorIdx) {
const IVec3 &minsTest = ptrobjt->posObj() + ptrobjt->_boundingBox.mins;
const IVec3 &maxsTest = ptrobjt->posObj() + ptrobjt->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
ptrobj->_objCol = a; // mark as collision with actor a
if (a != oldObjCol) {
debugC(1, TwinE::kDebugCollision, "Actor %d is colliding with %d", actorIdx, a);
}
if (ptrobjt->_flags.bIsCarrierActor) {
if (ptrobj->_workFlags.bIsFalling) {
// I touch a carrier
processActor.y = maxsTest.y - ptrobj->_boundingBox.mins.y + 1;
ptrobj->_carryBy = a;
continue;
} else if (checkZvOnZv(actorIdx, a)) {
// I walk on a carrier
processActor.y = maxsTest.y - ptrobj->_boundingBox.mins.y + 1;
ptrobj->_carryBy = a;
continue;
}
} else {
// I step on someone
if (checkZvOnZv(actorIdx, a)) {
_engine->_actor->hitObj(actorIdx, a, 1, -1);
}
}
handlePushing(processActor, minsTest, maxsTest, ptrobj, ptrobjt);
}
}
}
// test moves ZV further if hit
if (ptrobj->_workFlags.bIsHitting) {
const IVec2 &destPos = _engine->_renderer->rotate(0, 200, ptrobj->_beta);
mins = processActor + ptrobj->_boundingBox.mins;
mins.x += destPos.x;
mins.z += destPos.y;
maxs = processActor + ptrobj->_boundingBox.maxs;
maxs.x += destPos.x;
maxs.z += destPos.y;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
const ActorStruct *actorTest = _engine->_scene->getActor(a);
// avoid current processed actor
if (a != actorIdx && actorTest->_body != -1 && !actorTest->_flags.bIsInvisible && actorTest->_carryBy != actorIdx) {
const IVec3 minsTest = actorTest->posObj() + actorTest->_boundingBox.mins;
const IVec3 maxsTest = actorTest->posObj() + actorTest->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
_engine->_actor->hitObj(actorIdx, a, ptrobj->_hitForce, ptrobj->_beta + LBAAngles::ANGLE_180);
ptrobj->_workFlags.bIsHitting = 0;
}
}
}
}
return ptrobj->_objCol;
}
void Collision::setCollisionPos(const IVec3 &pos) {
_processCollision = pos;
}
uint32 Collision::doCornerReajustTwinkel(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask) {
IVec3 &processActor = actor->_processActor;
const IVec3 &oldPos = actor->_oldPos;
ShapeType orgcol = _engine->_grid->worldColBrick(processActor);
uint32 _col1 = 0;
processActor.x += x;
processActor.y += y;
processActor.z += z;
if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= SCENE_SIZE_MAX && processActor.z <= SCENE_SIZE_MAX) {
const BoundingBox &bbox = actor->_boundingBox;
reajustPos(processActor, orgcol);
ShapeType col = _engine->_grid->worldColBrickFull(processActor, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX);
if (col == ShapeType::kSolid) {
_col1 |= damageMask;
if (_engine->_grid->worldColBrickFull(processActor.x, processActor.y, oldPos.z + z, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX) == ShapeType::kSolid) {
if (_engine->_grid->worldColBrickFull(x + oldPos.x, processActor.y, processActor.z, bbox.maxs.y, OWN_ACTOR_SCENE_INDEX) != ShapeType::kSolid) {
_processCollision.x = oldPos.x;
}
} else {
_processCollision.z = oldPos.z;
}
}
}
processActor = _processCollision;
return _col1;
}
uint32 Collision::doCornerReajust(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask) {
IVec3 &processActor = actor->_processActor;
const IVec3 &previousActor = actor->_oldPos;
ShapeType orgcol = _engine->_grid->worldColBrick(processActor);
uint32 _col1 = 0;
processActor.x += x;
processActor.y += y;
processActor.z += z;
if (processActor.x >= 0 && processActor.z >= 0 && processActor.x <= SCENE_SIZE_MAX && processActor.z <= SCENE_SIZE_MAX) {
reajustPos(processActor, orgcol);
ShapeType col = _engine->_grid->worldColBrick(processActor);
if (col == ShapeType::kSolid) {
_col1 |= damageMask;
if (_engine->_grid->worldColBrick(processActor.x, processActor.y, previousActor.z + z) == ShapeType::kSolid) {
if (_engine->_grid->worldColBrick(x + previousActor.x, processActor.y, processActor.z) != ShapeType::kSolid) {
_processCollision.x = previousActor.x;
}
} else {
_processCollision.z = previousActor.z;
}
}
}
processActor = _processCollision;
return _col1;
}
void Collision::receptionObj(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (IS_HERO(actorIdx)) {
const IVec3 &processActor = actor->_processActor;
const int32 fall = _engine->_scene->_startYFalling - processActor.y;
if (fall >= SIZE_BRICK_Y * 8) {
const IVec3 &actorPos = actor->posObj();
_engine->_extra->initSpecial(actorPos.x, actorPos.y + 1000, actorPos.z, ExtraSpecialType::kHitStars);
if (fall >= SIZE_BRICK_Y * 16) {
actor->setLife(0);
} else {
actor->addLife(-1);
}
_engine->_animations->initAnim(AnimationTypes::kLandingHit, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
} else if (fall > 2 * SIZE_BRICK_Y) {
_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx);
} else {
if (actor->_workFlags.bWasWalkingBeforeFalling) {
// try to not interrupt walk animation if Twinsen falls down from small height
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kStanding, actorIdx);
} else {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kStanding, actorIdx);
}
}
_engine->_scene->_startYFalling = 0;
} else {
_engine->_animations->initAnim(AnimationTypes::kLanding, AnimType::kAnimationAllThen, actor->_nextGenAnim, actorIdx);
}
if (actor->_workFlags.bIsFalling) {
debugC(1, TwinE::kDebugCollision, "Actor %d reset falling", actorIdx);
}
actor->_workFlags.bIsFalling = 0;
actor->_workFlags.bWasWalkingBeforeFalling = 0;
}
int32 Collision::extraCheckObjCol(ExtraListStruct *extra, int32 actorIdx) {
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(extra->sprite);
const IVec3 mins = bbox->mins + extra->pos;
const IVec3 maxs = bbox->maxs + extra->pos;
for (int32 a = 0; a < _engine->_scene->_nbObjets; a++) {
const ActorStruct *actorTest = _engine->_scene->getActor(a);
if (a != actorIdx && actorTest->_body != -1) {
const IVec3 minsTest = actorTest->posObj() + actorTest->_boundingBox.mins;
const IVec3 maxsTest = actorTest->posObj() + actorTest->_boundingBox.maxs;
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
if (extra->strengthOfHit != 0) {
_engine->_actor->hitObj(actorIdx, a, extra->strengthOfHit, -1);
}
return a;
}
}
}
return -1;
}
bool Collision::fullWorldColBrick(int32 x, int32 y, int32 z, const IVec3 &oldPos) {
if (_engine->_grid->worldColBrick(oldPos) != ShapeType::kNone) {
return true;
}
const int32 averageX = ABS(x + oldPos.x) / 2;
const int32 averageY = ABS(y + oldPos.y) / 2;
const int32 averageZ = ABS(z + oldPos.z) / 2;
if (_engine->_grid->worldColBrick(averageX, averageY, averageZ) != ShapeType::kNone) {
return true;
}
if (_engine->_grid->worldColBrick(ABS(oldPos.x + averageX) / 2, ABS(oldPos.y + averageY) / 2, ABS(oldPos.z + averageZ) / 2) != ShapeType::kNone) {
return true;
}
if (_engine->_grid->worldColBrick(ABS(x + averageX) / 2, ABS(y + averageY) / 2, ABS(z + averageZ) / 2) != ShapeType::kNone) {
return true;
}
return false;
}
int32 Collision::extraCheckExtraCol(ExtraListStruct *extra, int32 extraIdx) const {
int32 index = extra->sprite;
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(index);
const IVec3 mins = bbox->mins + extra->pos;
const IVec3 maxs = bbox->maxs + extra->pos;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
const ExtraListStruct *extraTest = &_engine->_extra->_extraList[i];
if (i != extraIdx && extraTest->sprite != -1) {
const BoundingBox *testbbox = _engine->_resources->_spriteBoundingBox.bbox(++index);
const IVec3 minsTest = testbbox->mins + extraTest->pos;
const IVec3 maxsTest = testbbox->maxs + extraTest->pos;
if (mins.x >= minsTest.x) {
continue;
}
if (mins.x < maxsTest.x && maxs.x > minsTest.x && mins.y < maxsTest.y && maxs.y > minsTest.y && mins.z < maxsTest.z && maxs.z > minsTest.z) {
return i;
}
}
}
return -1;
}
void Collision::doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner) {
debugC(3, kDebugLevels::kDebugCollision, "Collision::doImpact(%i, %i, %i, %i, %i)", num, x, y, z, owner);
// TODO: Implement me
}
} // namespace TwinE

View File

@@ -0,0 +1,110 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_COLLISION_H
#define TWINE_SCENE_COLLISION_H
#include "common/scummsys.h"
#include "twine/shared.h"
namespace TwinE {
class ActorStruct;
struct ExtraListStruct;
class TwinEEngine;
class Collision {
private:
TwinEEngine *_engine;
void handlePushing(IVec3 &processActor, const IVec3 &minsTest, const IVec3 &maxsTest, ActorStruct *actor, ActorStruct *actorTest);
/** Actor collision coordinate */
IVec3 _processCollision; // SaveNxw, SaveNyw, SaveNzw
public:
Collision(TwinEEngine *engine);
/** Actor collision coordinate */
IVec3 _collision; // YMap
/**
* Check if actor 1 is standing in actor 2
* @param actorIdx1 Actor 1 index
* @param actorIdx2 Actor 2 index
*/
bool checkZvOnZv(int32 actorIdx1, int32 actorIdx2) const;
void doImpact(int32 num, int32 x, int32 y, int32 z, int32 owner);
/**
* Reajust actor position in scene according with brick shape bellow actor
* @param brickShape Shape of brick bellow the actor
*/
void reajustPos(IVec3 &processActor, ShapeType brickShape) const;
/**
* Check collision with actors
* @param actorIx Current process actor index
*/
int32 checkObjCol(int32 actorIdx);
bool checkValidObjPos(int32 actorIdx);
void setCollisionPos(const IVec3 &pos);
/**
* Check Hero collision with bricks
* @param x Hero X coordinate
* @param y Hero Y coordinate
* @param z Hero Z coordinate
* @param damageMask Cause damage mask
*/
uint32 doCornerReajustTwinkel(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask);
/**
* Check other actor collision with bricks
* @param x Actor X coordinate
* @param y Actor Y coordinate
* @param z Actor Z coordinate
* @param damageMask Cause damage mask
*/
uint32 doCornerReajust(ActorStruct *actor, int32 x, int32 y, int32 z, int32 damageMask);
/** Make actor to stop falling */
void receptionObj(int actorIdx);
/**
* Check extra collision with actors
* @param extra to process
* @param actorIdx actor to check collision
*/
int32 extraCheckObjCol(ExtraListStruct *extra, int32 actorIdx);
/** Check extra collision with bricks */
bool fullWorldColBrick(int32 x, int32 y, int32 z, const IVec3 &oldPos);
/**
* Check extra collision with another extra
* @param extra to process
* @param extraIdx extra index to check collision
*/
int32 extraCheckExtraCol(ExtraListStruct *extra, int32 extraIdx) const;
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,149 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/dart.h"
#include "twine/audio/sound.h"
#include "twine/renderer/redraw.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/scene.h"
namespace TwinE {
void Dart::InitDarts() {
int32 x0, x1, y0, y1, z0, z1;
#if 0
uint8 *ptrbody;
T_BODY_HEADER *ptr;
ptrbody = (uint8 *)GivePtrObjFix(BODY_3D_DART);
if (!ptrbody) {
char tmpFilePath[ADELINE_MAX_PATH];
GetResPath(tmpFilePath, ADELINE_MAX_PATH, OBJFIX_HQR_NAME);
TheEndCheckFile(tmpFilePath);
}
// Calcule ZV des flechettes
ptr = (T_BODY_HEADER *)ptrbody;
x0 = ptr->XMin;
x1 = ptr->XMax;
y0 = ptr->YMin;
y1 = ptr->YMax;
z0 = ptr->ZMin;
z1 = ptr->ZMax;
#else
x0 = x1 = y0 = y1 = z0 = z1 = 0;
#endif
// Average
int32 size = ((x1 - x0) + (z1 - z0)) / 4;
T_DART *ptrd = ListDart;
for (uint32 t = 0; t < MAX_DARTS; t++, ptrd++) {
ptrd->Body = BODY_3D_DART;
ptrd->XMin = -size;
ptrd->XMax = size;
ptrd->YMin = y0;
ptrd->YMax = y1;
ptrd->ZMin = -size;
ptrd->ZMax = size;
ptrd->Flags = 0;
ptrd->NumCube = -1;
}
}
int32 Dart::GetDart() {
T_DART *ptrd;
int32 t;
ptrd = ListDart;
for (t = 0; t < MAX_DARTS; t++, ptrd++) {
if (ptrd->Flags & DART_TAKEN) {
return t;
}
}
return -1;
}
void Dart::TakeAllDarts() {
T_DART *ptrd;
int32 n;
ptrd = ListDart;
for (n = 0; n < MAX_DARTS; n++, ptrd++) {
ptrd->Flags |= DART_TAKEN;
}
_engine->_gameState->setDarts(MAX_DARTS);
}
void Dart::CheckDartCol(ActorStruct *ptrobj) {
int32 n;
T_DART *ptrd;
int32 x0, y0, z0, x1, y1, z1;
int32 xt0, yt0, zt0, xt1, yt1, zt1;
if (ptrobj->_flags.bIsInvisible)
return;
x0 = ptrobj->_posObj.x + ptrobj->_boundingBox.mins.x;
x1 = ptrobj->_posObj.x + ptrobj->_boundingBox.maxs.x;
y0 = ptrobj->_posObj.y + ptrobj->_boundingBox.mins.y;
y1 = ptrobj->_posObj.y + ptrobj->_boundingBox.maxs.y;
z0 = ptrobj->_posObj.z + ptrobj->_boundingBox.mins.z;
z1 = ptrobj->_posObj.z + ptrobj->_boundingBox.maxs.z;
ptrd = ListDart;
for (n = 0; n < MAX_DARTS; n++, ptrd++) {
if (ptrd->NumCube == _engine->_scene->_numCube && !(ptrd->Flags & DART_TAKEN)) {
xt0 = ptrd->PosX + ptrd->XMin;
xt1 = ptrd->PosX + ptrd->XMax;
yt0 = ptrd->PosY + ptrd->YMin;
yt1 = ptrd->PosY + ptrd->YMax;
zt0 = ptrd->PosZ + ptrd->ZMin;
zt1 = ptrd->PosZ + ptrd->ZMax;
if (x0 < xt1 && x1 > xt0 && y0 < yt1 && y1 > yt0 && z0 < zt1 && z1 > zt0) {
ptrd->Flags |= DART_TAKEN;
_engine->_gameState->addDart();
#if 0
_engine->_sound->playSample(SAMPLE_BONUS_TROUVE, 0x1000, 0, 1,
ptrd->PosX, ptrd->PosY, ptrd->PosZ);
_engine->_redraw->addOverlay(OverlayType::koSprite | INCRUST_YCLIP,
SPRITE_DART,
15, 30,
0, 0, 2);
#endif
}
}
}
}
} // namespace TwinE

View File

@@ -0,0 +1,69 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_DART_H
#define TWINE_SCENE_DART_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
#define MAX_DARTS 3
#define BODY_3D_DART 61
// dart flags
#define DART_TAKEN (1 << 0)
namespace TwinE {
class Dart {
private:
TwinEEngine *_engine;
public:
struct T_DART {
int32 PosX = 0;
int32 PosY = 0;
int32 PosZ = 0;
int32 Alpha = 0;
int32 Beta = 0;
int32 Body = 0;
int32 NumCube = 0; // Number of the cube in which the dart is located
uint32 Flags = 0u;
int32 XMin = 0; // ZV of the darts
int32 YMin = 0;
int32 ZMin = 0;
int32 XMax = 0;
int32 YMax = 0;
int32 ZMax = 0;
};
T_DART ListDart[MAX_DARTS];
Dart(TwinEEngine *engine) : _engine(engine) {}
void InitDarts();
int32 GetDart();
void TakeAllDarts();
void CheckDartCol(ActorStruct *ptrobj);
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,824 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/extra.h"
#include "common/util.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/collision.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 {
/** Hit Stars shape info */
static const ShapeData hitStarsData[]{
{4, -6},
{19, -6},
{7, 2},
{12, 16},
{0, 7},
{-12, 16},
{-7, 2},
{-19, -6},
{-4, -6}};
/** Explode Cloud shape info */
static const ShapeData explodeCloudData[]{
{0, -20},
{6, -16},
{8, -10},
{14, -12},
{20, -4},
{18, 4},
{12, 4},
{16, 8},
{8, 16},
{2, 12},
{-4, 18},
{-10, 16},
{-12, 8},
{-16, 10},
{-20, 4},
{-12, -8},
{-6, -6},
{-10, -12}};
const ExtraShape hitStarsShape { ARRAYSIZE(hitStarsData), hitStarsData };
const ExtraShape explodeCloudShape { ARRAYSIZE(explodeCloudData), explodeCloudData };
Extra::Extra(TwinEEngine *engine) : _engine(engine) {}
int32 Extra::extraSearch(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActor, int32 maxSpeed, int32 strengthOfHit) { // ExtraSearch
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::SEARCH_OBJ;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.actorIdx = actorIdx;
extra->spawnTime = targetActor;
extra->destPos.z = maxSpeed;
extra->strengthOfHit = strengthOfHit;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, maxSpeed, LBAAngles::ANGLE_17, &extra->trackActorMove);
const ActorStruct *actor = _engine->_scene->getActor(targetActor);
extra->angle = _engine->_movements->getAngle(extra->pos, actor->posObj());
return i;
}
return -1;
}
int32 Extra::addExtraExplode(int32 x, int32 y, int32 z) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = SPRITEHQR_EXPLOSION_FIRST_FRAME;
extra->type = ExtraType::TIME_OUT | ExtraType::EXPLOSION;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.lifeTime = 40;
extra->spawnTime = _engine->timerRef;
extra->strengthOfHit = 0;
return i;
}
return -1;
}
void Extra::clearExtra() {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
extra->sprite = -1;
extra->info1 = 1;
}
}
void Extra::initFly(ExtraListStruct *extra, int32 xAngle, int32 yAngle, int32 x, int32 extraAngle) {
extra->type |= ExtraType::FLY;
extra->lastPos = extra->pos;
IVec2 destPos = _engine->_renderer->rotate(x, 0, xAngle);
extra->destPos.y = -destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, yAngle);
extra->destPos.x = destPos.x;
extra->destPos.z = destPos.y;
extra->angle = extraAngle;
extra->spawnTime = _engine->timerRef;
}
int32 Extra::initSpecial(int32 x, int32 y, int32 z, ExtraSpecialType type) {
const int16 flag = EXTRA_SPECIAL_MASK + (int16)type;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = flag;
extra->info1 = 0;
if (type == ExtraSpecialType::kHitStars) {
extra->type = ExtraType::TIME_OUT | ExtraType::END_COL;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, _engine->getRandomNumber(LBAAngles::ANGLE_90) + LBAAngles::ANGLE_45, _engine->getRandomNumber(LBAAngles::ANGLE_360), 50, 20);
extra->strengthOfHit = 0;
extra->payload.lifeTime = 100;
} else if (type == ExtraSpecialType::kExplodeCloud) {
extra->type = ExtraType::TIME_OUT;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->strengthOfHit = 0;
extra->spawnTime = _engine->timerRef;
extra->payload.lifeTime = 5;
}
return i;
}
return -1;
}
int Extra::getBonusSprite(BonusParameter bonusParameter) const {
int numBonus = 0;
int8 bonusSprites[5];
if (bonusParameter.kashes) {
bonusSprites[numBonus++] = SPRITEHQR_KASHES;
}
if (bonusParameter.lifepoints) {
bonusSprites[numBonus++] = SPRITEHQR_LIFEPOINTS;
}
if (bonusParameter.magicpoints) {
bonusSprites[numBonus++] = SPRITEHQR_MAGICPOINTS;
}
if (bonusParameter.key) {
bonusSprites[numBonus++] = SPRITEHQR_KEY;
}
if (bonusParameter.cloverleaf) {
bonusSprites[numBonus++] = SPRITEHQR_CLOVERLEAF;
}
if (numBonus == 0) {
return -1;
}
const int bonusIndex = _engine->getRandomNumber(numBonus);
assert(bonusIndex >= 0);
assert(bonusIndex < numBonus);
int8 bonusSprite = bonusSprites[bonusIndex];
// if bonus is magic and no magic level yet, then give life points
if (!_engine->_gameState->_magicLevelIdx && bonusSprite == SPRITEHQR_MAGICPOINTS) {
bonusSprite = SPRITEHQR_LIFEPOINTS;
}
return bonusSprite;
}
int32 Extra::addExtraBonus(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = type;
extra->type = ExtraType::STOP_COL | ExtraType::TAKABLE | ExtraType::WAIT_SOME_TIME;
if (type != SPRITEHQR_KEY) {
extra->type |= ExtraType::TIME_OUT | ExtraType::FLASH;
}
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, xAngle, yAngle, 40, ToAngle(15));
extra->strengthOfHit = 0;
extra->payload.lifeTime = _engine->toSeconds(20);
extra->info1 = bonusAmount;
return i;
}
return -1;
}
int32 Extra::throwExtra(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle, int32 strengthOfHit) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::END_OBJ | ExtraType::END_COL | ExtraType::IMPACT | ExtraType::WAIT_NO_COL;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
initFly(extra, xAngle, yAngle, xRotPoint, extraAngle);
extra->strengthOfHit = strengthOfHit;
extra->spawnTime = _engine->timerRef;
extra->payload.actorIdx = actorIdx;
extra->info1 = 0;
return i;
}
return -1;
}
int32 Extra::addExtraAiming(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActorIdx, int32 finalAngle, int32 strengthOfHit) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::SEARCH_OBJ;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.actorIdx = actorIdx;
extra->spawnTime = targetActorIdx;
extra->destPos.z = finalAngle;
extra->strengthOfHit = strengthOfHit;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, finalAngle, LBAAngles::ANGLE_17, &extra->trackActorMove);
const ActorStruct *actor = _engine->_scene->getActor(targetActorIdx);
extra->angle = _engine->_movements->getAngle(extra->pos, actor->posObj());
return i;
}
return -1;
}
int32 Extra::searchBonusKey() const {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
const ExtraListStruct *extra = &_extraList[i];
if (extra->sprite == SPRITEHQR_KEY) {
return i;
}
}
return -1;
}
int32 Extra::extraSearchKey(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 extraIdx) {
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite != -1) {
continue;
}
extra->sprite = spriteIdx;
extra->type = ExtraType::MAGIC_BALL_KEY;
extra->info1 = 0;
extra->pos.x = x;
extra->pos.y = y;
extra->pos.z = z;
extra->payload.extraIdx = extraIdx;
extra->destPos.z = 4000;
extra->strengthOfHit = 0;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, 4000, LBAAngles::ANGLE_17, &extra->trackActorMove);
extra->angle = _engine->_movements->getAngle(extra->pos, _extraList[extraIdx].pos);
return i;
}
return -1;
}
void Extra::addExtraThrowMagicball(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle) {
int32 ballSprite = -1;
int32 ballStrength = 0;
switch (_engine->_gameState->_magicLevelIdx) {
case 0:
case 1:
ballSprite = SPRITEHQR_MAGICBALL_YELLOW;
ballStrength = 4;
break;
case 2:
ballSprite = SPRITEHQR_MAGICBALL_GREEN;
ballStrength = 6;
break;
case 3:
ballSprite = SPRITEHQR_MAGICBALL_RED;
ballStrength = 8;
break;
case 4:
ballSprite = SPRITEHQR_MAGICBALL_FIRE;
ballStrength = 10;
break;
}
_engine->_gameState->_magicBallType = ((_engine->_gameState->_magicPoint - 1) / 20) + 1;
if (_engine->_gameState->_magicPoint == 0) {
_engine->_gameState->_magicBallType = 0;
}
const int32 extraIdx = searchBonusKey();
if (extraIdx != -1) { // there is a key to aim
_engine->_gameState->_magicBallType = 5;
}
switch (_engine->_gameState->_magicBallType) {
case 0:
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 1:
_engine->_gameState->_magicBallCount = 4;
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 2:
case 3:
case 4:
_engine->_gameState->_magicBallType = 1;
_engine->_gameState->_magicBallCount = 4;
_engine->_gameState->_magicBall = throwExtra(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, xAngle, yAngle, xRotPoint, extraAngle, ballStrength);
break;
case 5:
assert(extraIdx != -1);
_engine->_gameState->_magicBall = extraSearchKey(OWN_ACTOR_SCENE_INDEX, x, y, z, ballSprite, extraIdx);
break;
}
if (_engine->_gameState->_magicPoint > 0) {
_engine->_gameState->_magicPoint--;
}
}
void Extra::aff2DShape(const ExtraShape &shapeTable, int32 x, int32 y, int32 color, int32 angle, int32 zoom, Common::Rect &renderRect) {
int shapeDataIndex = 0;
int16 shapeX = shapeTable.data[shapeDataIndex].x * zoom / 16;
int16 shapeZ = shapeTable.data[shapeDataIndex].z * zoom / 16;
++shapeDataIndex;
_engine->clearScreenMinMax(renderRect);
IVec2 destPos = _engine->_renderer->rotate(shapeX, shapeZ, angle);
const int32 computedX = destPos.x + x;
const int32 computedY = destPos.y + y;
_engine->adjustScreenMax(renderRect, computedX, computedY);
int32 currentX = computedX;
int32 currentY = computedY;
for (int32 numEntries = 1; numEntries < shapeTable.n; ++numEntries) {
shapeX = shapeTable.data[shapeDataIndex].x * zoom / 16;
shapeZ = shapeTable.data[shapeDataIndex].z * zoom / 16;
++shapeDataIndex;
const int32 oldComputedX = currentX;
const int32 oldComputedY = currentY;
destPos = _engine->_renderer->rotate(shapeX, shapeZ, angle);
currentX = destPos.x + x;
currentY = destPos.y + y;
_engine->adjustScreenMax(renderRect, currentX, currentY);
_engine->_interface->drawLine(oldComputedX, oldComputedY, currentX, currentY, color);
}
_engine->_interface->drawLine(currentX, currentY, computedX, computedY, color);
}
void Extra::affSpecial(int32 extraIdx, int32 x, int32 y, Common::Rect &renderRect) {
ExtraListStruct *extra = &_extraList[extraIdx];
ExtraSpecialType specialType = (ExtraSpecialType)(extra->sprite & (EXTRA_SPECIAL_MASK - 1));
switch (specialType) {
case ExtraSpecialType::kHitStars:
aff2DShape(hitStarsShape, x, y, COLOR_WHITE, (_engine->timerRef * 32) & LBAAngles::ANGLE_270, 4, renderRect);
break;
case ExtraSpecialType::kExplodeCloud: {
int32 zoom = 1 + _engine->timerRef - extra->spawnTime;
if (zoom > 32) {
zoom = 32;
}
aff2DShape(explodeCloudShape, x, y, COLOR_WHITE, LBAAngles::ANGLE_0, zoom, renderRect);
break;
}
case ExtraSpecialType::kFountain:
break;
}
}
void Extra::bounceExtra(ExtraListStruct *extra, int32 x, int32 y, int32 z) {
if (_engine->_grid->worldColBrick(x, extra->pos.y, z) != ShapeType::kNone) {
extra->destPos.y = -extra->destPos.y;
}
if (_engine->_grid->worldColBrick(extra->pos.x, y, z) != ShapeType::kNone) {
extra->destPos.x = -extra->destPos.x;
}
if (_engine->_grid->worldColBrick(x, y, extra->pos.z) != ShapeType::kNone) {
extra->destPos.z = -extra->destPos.z;
}
extra->pos.x = x;
extra->lastPos.x = x;
extra->pos.y = y;
extra->lastPos.y = y;
extra->pos.z = z;
extra->lastPos.z = z;
extra->spawnTime = _engine->timerRef;
}
void Extra::gereExtras() {
int32 currentExtraX = 0;
int32 currentExtraY = 0;
int32 currentExtraZ = 0;
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_extraList[i];
if (extra->sprite == -1) {
continue;
}
// process extra life time
if (extra->type & ExtraType::TIME_OUT) {
if (extra->payload.lifeTime + extra->spawnTime <= _engine->timerRef) {
extra->sprite = -1;
continue;
}
}
// reset extra
if (extra->type & ExtraType::ONE_FRAME) {
extra->sprite = -1;
continue;
}
const int32 deltaT = _engine->timerRef - extra->spawnTime;
if (extra->type & ExtraType::EXPLOSION) {
extra->sprite = boundRuleThree(SPRITEHQR_EXPLOSION_FIRST_FRAME, 100, 30, deltaT);
continue;
}
// process extra moving
if (extra->type & ExtraType::FLY) {
currentExtraX = extra->pos.x;
currentExtraY = extra->pos.y;
currentExtraZ = extra->pos.z;
const int32 currentExtraSpeedX = extra->destPos.x * deltaT;
extra->pos.x = currentExtraSpeedX + extra->lastPos.x;
const int32 currentExtraSpeedY = extra->destPos.y * deltaT;
extra->pos.y = currentExtraSpeedY + extra->lastPos.y - ABS(extra->angle * deltaT * deltaT / 16);
extra->pos.z = extra->destPos.z * deltaT + extra->lastPos.z;
// check if extra is out of scene
if (extra->pos.y < 0 || extra->pos.x < 0 || extra->pos.x > SCENE_SIZE_MAX || extra->pos.z < 0 || extra->pos.z > SCENE_SIZE_MAX) {
// if extra is Magic Ball
if (i == _engine->_gameState->_magicBall) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, OWN_ACTOR_SCENE_INDEX, 10000, 0);
}
// if can take extra on ground
if (extra->type & ExtraType::TAKABLE) {
extra->type &= ~(ExtraType::FLY | ExtraType::STOP_COL);
} else {
extra->sprite = -1;
}
continue;
}
}
if (extra->type & ExtraType::WAIT_SOME_TIME) {
if (_engine->timerRef - extra->spawnTime > 40) {
extra->type &= ~ExtraType::WAIT_SOME_TIME;
}
continue;
}
// process actor target hit
if (extra->type & ExtraType::SEARCH_OBJ) {
int32 actorIdxAttacked = extra->spawnTime;
int32 actorIdx = extra->payload.actorIdx;
const ActorStruct *actor = _engine->_scene->getActor(actorIdxAttacked);
currentExtraX = actor->_posObj.x;
currentExtraY = actor->_posObj.y + 1000;
currentExtraZ = actor->_posObj.z;
const int32 tmpAngle = _engine->_movements->getAngle(extra->pos, actor->posObj());
const int32 angle = ClampAngle(tmpAngle - extra->angle);
if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
if (extra->strengthOfHit) {
_engine->_actor->hitObj(actorIdx, actorIdxAttacked, extra->strengthOfHit, -1);
}
if (i == _engine->_gameState->_magicBall) {
_engine->_gameState->_magicBall = -1;
}
extra->sprite = -1;
continue;
}
const int32 angle2 = _engine->_movements->getAngle(extra->pos.y, 0, currentExtraY, _engine->_movements->_targetActorDistance);
int32 pos = extra->trackActorMove.getRealValueFromTime(_engine->timerRef);
if (!pos) {
pos = 1;
}
IVec2 destPos = _engine->_renderer->rotate(pos, 0, angle2);
extra->pos.y -= destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, tmpAngle);
extra->pos.x += destPos.x;
extra->pos.z += destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
if (actorIdxAttacked == _engine->_collision->extraCheckObjCol(extra, actorIdx)) {
if (i == _engine->_gameState->_magicBall) {
_engine->_gameState->_magicBall = -1;
}
extra->sprite = -1;
continue;
}
}
// process magic ball extra aiming for key
if (extra->type & ExtraType::MAGIC_BALL_KEY) {
ExtraListStruct *extraKey = &_extraList[extra->payload.extraIdx];
const int32 extraIdx = extra->payload.extraIdx;
const int32 tmpAngle = _engine->_movements->getAngle(extra->pos, extraKey->pos);
const int32 angle = ClampAngle(tmpAngle - extra->angle);
if (angle > LBAAngles::ANGLE_140 && angle < LBAAngles::ANGLE_210) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
if (extraKey->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
_engine->_redraw->addOverlay(OverlayType::koNumber, extraKey->info1, projPos.x, projPos.y, COLOR_BLACK, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, SPRITEHQR_KEY, 10, 30, 0, OverlayPosType::koNormal, 2);
_engine->_gameState->addKeys(extraKey->info1);
extraKey->sprite = -1;
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, SPRITEHQR_KEY, 0, 8000, 0);
continue;
}
const int32 angle2 = _engine->_movements->getAngle(extra->pos.y, 0, extraKey->pos.y, _engine->_movements->_targetActorDistance);
int32 pos = extra->trackActorMove.getRealValueFromTime(_engine->timerRef);
if (!pos) {
pos = 1;
}
IVec2 destPos = _engine->_renderer->rotate(pos, 0, angle2);
extra->pos.y -= destPos.y;
destPos = _engine->_renderer->rotate(0, destPos.x, tmpAngle);
extra->pos.x += destPos.x;
extra->pos.z += destPos.y;
_engine->_movements->initRealValue(LBAAngles::ANGLE_0, extra->destPos.z, LBAAngles::ANGLE_17, &extra->trackActorMove);
if (extraIdx == _engine->_collision->extraCheckExtraCol(extra, _engine->_gameState->_magicBall)) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, _engine->_scene->_sceneHero->posObj(), OWN_ACTOR_SCENE_INDEX);
if (extraKey->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extraKey->pos - _engine->_grid->_worldCube);
_engine->_redraw->addOverlay(OverlayType::koNumber, extraKey->info1, projPos.x, projPos.y, COLOR_BLACK, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, SPRITEHQR_KEY, 10, 30, 0, OverlayPosType::koNormal, 2);
_engine->_gameState->addKeys(extraKey->info1);
extraKey->sprite = -1;
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, SPRITEHQR_KEY, 0, 8000, 0);
continue;
}
if (extraKey->sprite == -1) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
extra->sprite = -1;
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, 0, 8000, 0);
continue;
}
}
// process extra collision with actors
if (extra->type & ExtraType::END_OBJ) {
if (_engine->_collision->extraCheckObjCol(extra, extra->payload.actorIdx) != -1) {
// if extra is Magic Ball
if (i == _engine->_gameState->_magicBall) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z,
spriteIdx, 0, 10000, 0);
}
extra->sprite = -1;
continue;
}
}
// process extra collision with scene ground
if (extra->type & ExtraType::END_COL) {
bool flagcol = false;
if (_engine->_collision->fullWorldColBrick(currentExtraX, currentExtraY, currentExtraZ, extra->pos)) {
// if not touch the ground
if (!(extra->type & ExtraType::WAIT_NO_COL)) {
flagcol = true;
}
} else {
// if touch the ground
if (extra->type & ExtraType::WAIT_NO_COL) {
extra->type &= ~ExtraType::WAIT_NO_COL; // set flag out of ground
}
}
if (flagcol) {
// show explode cloud
if (extra->type & ExtraType::IMPACT) {
initSpecial(currentExtraX, currentExtraY, currentExtraZ, ExtraSpecialType::kExplodeCloud);
}
// if extra is magic ball
if (i == _engine->_gameState->_magicBall) {
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(300) - 150;
_engine->_sound->mixSample3D(Samples::Hit, pitchBend, 1, extra->pos, -1);
// can't bounce with not magic points
if (_engine->_gameState->_magicBallType <= 0) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, spriteIdx, 0, 10000, 0);
extra->sprite = -1;
continue;
}
// if has magic points
if (_engine->_gameState->_magicBallType == 1) {
if (!_engine->_gameState->_magicBallCount--) {
int32 spriteIdx = SPRITEHQR_MAGICBALL_YELLOW_TRANS;
if (extra->sprite == SPRITEHQR_MAGICBALL_GREEN) {
spriteIdx = SPRITEHQR_MAGICBALL_GREEN_TRANS;
}
if (extra->sprite == SPRITEHQR_MAGICBALL_RED) {
spriteIdx = SPRITEHQR_MAGICBALL_RED_TRANS;
}
_engine->_gameState->_magicBall = extraSearch(-1, extra->pos.x, extra->pos.y, extra->pos.z, spriteIdx, 0, 10000, 0);
extra->sprite = -1;
continue;
}
bounceExtra(extra, currentExtraX, currentExtraY, currentExtraZ);
}
} else {
extra->sprite = -1;
continue;
}
}
}
// extra stop moving while collision with bricks
if (extra->type & ExtraType::STOP_COL) {
bool process = false;
if (_engine->_collision->fullWorldColBrick(currentExtraX, currentExtraY, currentExtraZ, extra->pos)) {
// if not touch the ground
if (!(extra->type & ExtraType::WAIT_NO_COL)) {
process = true;
}
} else {
// if touch the ground
if (extra->type & ExtraType::WAIT_NO_COL) {
extra->type &= ~ExtraType::WAIT_NO_COL; // set flag out of ground
}
}
if (process) {
const BoundingBox *bbox = _engine->_resources->_spriteBoundingBox.bbox(extra->sprite);
extra->pos.y = (_engine->_collision->_collision.y * SIZE_BRICK_Y) + SIZE_BRICK_Y - bbox->mins.y;
extra->type &= ~(ExtraType::STOP_COL | ExtraType::FLY);
continue;
}
}
// get extras on ground
if ((extra->type & ExtraType::TAKABLE) && !(extra->type & ExtraType::FLY)) {
// if hero touch extra
if (_engine->_collision->extraCheckObjCol(extra, -1) == 0) {
_engine->_sound->mixSample3D(Samples::ItemFound, 0x1000, 1, extra->pos, -1);
if (extra->info1 > 1) {
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
const int16 fontColor = COLOR_158;
_engine->_redraw->addOverlay(OverlayType::koNumber, extra->info1, projPos.x, projPos.y, fontColor, OverlayPosType::koNormal, 2);
}
_engine->_redraw->addOverlay(OverlayType::koSprite, extra->sprite, 10, 30, 0, OverlayPosType::koNormal, 2);
if (extra->sprite == SPRITEHQR_KASHES) {
_engine->_gameState->addKashes(extra->info1);
} else if (extra->sprite == SPRITEHQR_LIFEPOINTS) {
_engine->_scene->_sceneHero->addLife(extra->info1);
} else if (extra->sprite == SPRITEHQR_MAGICPOINTS && _engine->_gameState->_magicLevelIdx) {
_engine->_gameState->addMagicPoints(extra->info1 * 2);
} else if (extra->sprite == SPRITEHQR_KEY) {
_engine->_gameState->addKeys(extra->info1);
} else if (extra->sprite == SPRITEHQR_CLOVERLEAF) {
_engine->_gameState->addLeafs(extra->info1);
}
extra->sprite = -1;
}
}
}
}
} // namespace TwinE

139
engines/twine/scene/extra.h Normal file
View File

@@ -0,0 +1,139 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_EXTRA_H
#define TWINE_SCENE_EXTRA_H
#include "common/scummsys.h"
#include "common/rect.h"
#include "twine/scene/actor.h"
namespace TwinE {
#define EXTRA_MAX_ENTRIES 50
#define EXTRA_SPECIAL_MASK 0x8000
struct ShapeData {
int16 x;
int16 z;
};
struct ExtraShape {
int n;
const ShapeData *data;
};
enum ExtraType {
TIME_OUT = 1 << 0, // 0x0001
FLY = 1 << 1, // 0x0002
END_OBJ = 1 << 2, // 0x0004
END_COL = 1 << 3, // 0x0008
STOP_COL = 1 << 4, // 0x0010
TAKABLE = 1 << 5, // 0x0020
FLASH = 1 << 6, // 0x0040
SEARCH_OBJ = 1 << 7, // 0x0080
IMPACT = 1 << 8, // 0x0100
MAGIC_BALL_KEY = 1 << 9, // 0x0200
TIME_IN = 1 << 10, // 0x0400
ONE_FRAME = 1 << 11, // 0x0800
EXPLOSION = 1 << 12, // 0x1000 EXTRA_EXPLO
WAIT_NO_COL = 1 << 13, // 0x2000 EXTRA_WAIT_NO_COL
WAIT_SOME_TIME = 1 << 14, // 0x4000
COMPUTE_TRAJ = 1 << 15 // 0x8000 used in dotemu enhanced to render the magic ball trajectories
};
struct ExtraListStruct {
int16 sprite = 0; /**< a value of -1 indicates that this instance is free to use */
IVec3 pos;
IVec3 lastPos;
IVec3 destPos;
RealValue trackActorMove;
uint16 type = 0; /**< ExtraType bitmask */
int16 angle = 0; // weight
int32 spawnTime = 0; // memo timer 50hz
union payload { // field_ 1C
int16 lifeTime;
int16 actorIdx;
int16 extraIdx;
int16 unknown;
} payload{0};
int16 strengthOfHit = 0; // apply damage if != 0
int16 info1 = 0; // various - number for zone giver
};
class TwinEEngine;
class Extra {
private:
TwinEEngine *_engine;
void initFly(ExtraListStruct *extra, int32 xAngle, int32 yAngle, int32 x, int32 extraAngle);
void bounceExtra(ExtraListStruct *extra, int32 x, int32 y, int32 z);
int32 searchBonusKey() const;
int32 extraSearchKey(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 extraIdx);
void aff2DShape(const ExtraShape &shapeTable, int32 x, int32 y, int32 color, int32 angle, int32 zoom, Common::Rect &renderRect);
public:
Extra(TwinEEngine *engine);
ExtraListStruct _extraList[EXTRA_MAX_ENTRIES];
int32 extraSearch(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActor, int32 maxSpeed, int32 strengthOfHit);
/**
* Add extra explosion
* @param x Explosion X coordinate
* @param y Explosion Y coordinate
* @param z Explosion Z coordinate
*/
int32 addExtraExplode(int32 x, int32 y, int32 z);
inline int32 extraExplo(const IVec3 &pos) {
return addExtraExplode(pos.x, pos.y, pos.z);
}
/** Reset all used extras */
void clearExtra();
int32 initSpecial(int32 x, int32 y, int32 z, ExtraSpecialType type);
int32 addExtraBonus(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount);
inline int32 addExtraBonus(const IVec3 &pos, int32 xAngle, int32 yAngle, int32 type, int32 bonusAmount) {
return addExtraBonus(pos.x, pos.y, pos.z, xAngle, yAngle, type, bonusAmount);
}
int32 throwExtra(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle, int32 strengthOfHit);
int32 addExtraAiming(int32 actorIdx, int32 x, int32 y, int32 z, int32 spriteIdx, int32 targetActorIdx, int32 finalAngle, int32 strengthOfHit);
void addExtraThrowMagicball(int32 x, int32 y, int32 z, int32 xAngle, int32 yAngle, int32 xRotPoint, int32 extraAngle);
void affSpecial(int32 extraIdx, int32 x, int32 y, Common::Rect &renderRect);
int getBonusSprite(BonusParameter bonusParameter) const;
/** Process extras */
void gereExtras();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,681 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/gamestate.h"
#include "common/rect.h"
#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/menu/menuoptions.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
#include "twine/twine.h"
#define SIZE_FOUND_OBJ 130
namespace TwinE {
GameState::GameState(TwinEEngine *engine) : _engine(engine) {
clearGameFlags();
Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
Common::fill(&_holomapFlags[0], &_holomapFlags[MAX_HOLO_POS_2], 0);
Common::fill(&_gameListChoice[0], &_gameListChoice[10], TextId::kNone);
}
void GameState::init3DGame() {
_engine->_renderer->setIsoProjection(_engine->width() / 2 - 8 - 1, _engine->height() / 2, SIZE_BRICK_XZ);
_engine->_renderer->setPosCamera(0, 0, 0);
_engine->_renderer->setAngleCamera(LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0);
_engine->_renderer->setLightVector(_engine->_scene->_alphaLight, _engine->_scene->_betaLight, LBAAngles::ANGLE_0);
}
void GameState::initGameStateVars() {
debug(2, "Init game state variables");
_engine->_extra->clearExtra();
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
_engine->_redraw->overlayList[i].num = -1;
}
for (int32 i = 0; i < ARRAYSIZE(_engine->_scene->_listFlagCube); i++) {
_engine->_scene->_listFlagCube[i] = 0;
}
clearGameFlags();
Common::fill(&_inventoryFlags[0], &_inventoryFlags[NUM_INVENTORY_ITEMS], 0);
_engine->_scene->initSceneVars();
Common::fill(&_holomapFlags[0], &_holomapFlags[MAX_HOLO_POS_2], 0);
}
void GameState::initHeroVars() {
_engine->_actor->initObject(OWN_ACTOR_SCENE_INDEX); // reset Hero
_magicBall = -1;
_inventoryNumLeafsBox = 2;
_inventoryNumLeafs = 2;
_goldPieces = 0;
_nbLittleKeys = 0;
_magicPoint = 0;
_weapon = false;
_engine->_scene->_sceneHero->_genBody = BodyType::btNormal;
_engine->_scene->_sceneHero->setLife(_engine->getMaxLife());
_engine->_scene->_sceneHero->_talkColor = COLOR_BRIGHT_BLUE;
}
void GameState::initEngineVars() {
debug(2, "Init engine variables");
_engine->_interface->unsetClip();
_engine->_scene->_alphaLight = LBAAngles::ANGLE_315;
_engine->_scene->_betaLight = LBAAngles::ANGLE_334;
init3DGame();
initGameStateVars();
initHeroVars();
_engine->_scene->_sceneStart.x = 16 * SIZE_BRICK_XZ;
_engine->_scene->_sceneStart.y = 24 * SIZE_BRICK_Y;
_engine->_scene->_sceneStart.z = 16 * SIZE_BRICK_XZ;
_engine->_scene->_numCube = SCENE_CEILING_GRID_FADE_1;
_engine->_scene->_newCube = LBA1SceneId::Citadel_Island_Prison;
_engine->_sceneLoopState = SceneLoopState::Continue;
_engine->_scene->_mecaPenguinIdx = -1;
_engine->_menuOptions->flagCredits = false;
_inventoryNumLeafs = 0;
_inventoryNumLeafsBox = 2;
_magicPoint = 0;
_goldPieces = 0;
_nbLittleKeys = 0;
_inventoryNumGas = 0;
_engine->_actor->_cropBottomScreen = 0;
_magicLevelIdx = 0;
_weapon = false;
setChapter(0);
_engine->_scene->_sceneTextBank = TextBankId::Options_and_menus;
_engine->_scene->_numObjFollow = OWN_ACTOR_SCENE_INDEX;
_engine->_actor->_heroBehaviour = HeroBehaviourType::kNormal;
_engine->_actor->_previousHeroAngle = 0;
_engine->_actor->_previousHeroBehaviour = HeroBehaviourType::kNormal;
}
// https://web.archive.org/web/*/http://lbafileinfo.kazekr.net/index.php?title=LBA1:Savegame
bool GameState::loadGame(Common::SeekableReadStream *file) {
if (file == nullptr) {
return false;
}
if (!_engine->isLBA1()) {
warning("Loading not implemented for lba2");
return false;
}
debug(2, "Load game");
const byte saveFileVersion = file->readByte();
// 4 is dotemu enhanced version of lba1
if (saveFileVersion != 3 && saveFileVersion != 4) {
warning("Could not load savegame - wrong magic byte");
return false;
}
initEngineVars();
int playerNameIdx = 0;
do {
const byte c = file->readByte();
_engine->_menuOptions->_saveGameName[playerNameIdx++] = c;
if (c == '\0') {
break;
}
if (playerNameIdx >= ARRAYSIZE(_engine->_menuOptions->_saveGameName)) {
warning("Failed to load savegame. Invalid playername.");
return false;
}
} while (true);
byte numGameFlags = file->readByte();
if (numGameFlags != NUM_GAME_FLAGS_LBA1) {
warning("Failed to load gameflags. Expected %u, but got %u", NUM_GAME_FLAGS_LBA1, numGameFlags);
return false;
}
for (uint8 i = 0; i < numGameFlags; ++i) {
setGameFlag(i, file->readByte());
}
_engine->_scene->_newCube = file->readByte(); // scene index
setChapter(file->readByte());
_engine->_actor->_heroBehaviour = (HeroBehaviourType)file->readByte();
_engine->_actor->_previousHeroBehaviour = _engine->_actor->_heroBehaviour;
_engine->_scene->_sceneHero->setLife(file->readByte());
setKashes(file->readSint16LE());
_magicLevelIdx = file->readByte();
setMagicPoints(file->readByte());
setLeafBoxes(file->readByte());
_engine->_scene->_sceneStart.x = file->readSint16LE();
_engine->_scene->_sceneStart.y = file->readSint16LE();
_engine->_scene->_sceneStart.z = file->readSint16LE();
_engine->_scene->_sceneHero->_beta = ToAngle(file->readSint16LE());
_engine->_actor->_previousHeroAngle = _engine->_scene->_sceneHero->_beta;
_engine->_scene->_sceneHero->_genBody = (BodyType)file->readByte();
const byte numHolomapFlags = file->readByte(); // number of holomap locations
if (numHolomapFlags != _engine->numHoloPos()) {
warning("Failed to load holomapflags. Got %u, expected %i", numHolomapFlags, _engine->numHoloPos());
return false;
}
file->read(_holomapFlags, _engine->numHoloPos());
setGas(file->readByte());
const byte numInventoryFlags = file->readByte(); // number of used inventory items, always 28
if (numInventoryFlags != NUM_INVENTORY_ITEMS) {
warning("Failed to load inventoryFlags. Got %u, expected %i", numInventoryFlags, NUM_INVENTORY_ITEMS);
return false;
}
file->read(_inventoryFlags, NUM_INVENTORY_ITEMS);
setLeafs(file->readByte());
_weapon = file->readByte();
if (saveFileVersion == 4) {
// the time the game was played
file->readUint32LE();
file->readUint32LE();
}
_engine->_scene->_numCube = SCENE_CEILING_GRID_FADE_1;
_engine->_scene->_flagChgCube = ScenePositionType::kReborn;
return true;
}
bool GameState::saveGame(Common::WriteStream *file) {
debug(2, "Save game");
if (!_engine->isLBA1()) {
warning("Saving not implemented for lba2");
return false;
}
if (_engine->_menuOptions->_saveGameName[0] == '\0') {
Common::strlcpy(_engine->_menuOptions->_saveGameName, "TwinEngineSave", sizeof(_engine->_menuOptions->_saveGameName));
}
int32 sceneIdx = _engine->_scene->_numCube;
if (sceneIdx == Polar_Island_end_scene || sceneIdx == Citadel_Island_end_sequence_1 || sceneIdx == Citadel_Island_end_sequence_2 || sceneIdx == Credits_List_Sequence) {
/* inventoryMagicPoints = 0x50 */
/* herobehaviour = 0 */
/* newheropos.x = 0xffff */
sceneIdx = Polar_Island_Final_Battle;
}
file->writeByte(0x03);
file->writeString(_engine->_menuOptions->_saveGameName);
file->writeByte('\0');
file->writeByte(NUM_GAME_FLAGS_LBA1);
for (uint8 i = 0; i < NUM_GAME_FLAGS_LBA1; ++i) {
file->writeByte((uint8)hasGameFlag(i));
}
file->writeByte(sceneIdx);
file->writeByte(getChapter());
file->writeByte((byte)_engine->_actor->_heroBehaviour);
file->writeByte(_engine->_scene->_sceneHero->_lifePoint);
file->writeSint16LE(_goldPieces);
file->writeByte(_magicLevelIdx);
file->writeByte(_magicPoint);
file->writeByte(_inventoryNumLeafsBox);
// we don't save the whole scene state - so we have to make sure that the hero is
// respawned at the start of the scene - and not at its current position
file->writeSint16LE(_engine->_scene->_sceneStart.x);
file->writeSint16LE(_engine->_scene->_sceneStart.y);
file->writeSint16LE(_engine->_scene->_sceneStart.z);
file->writeSint16LE(FromAngle(_engine->_scene->_sceneHero->_beta));
file->writeByte((uint8)_engine->_scene->_sceneHero->_genBody);
// number of holomap locations
file->writeByte(_engine->numHoloPos());
file->write(_holomapFlags, _engine->numHoloPos());
file->writeByte(_inventoryNumGas);
// number of inventory items
file->writeByte(NUM_INVENTORY_ITEMS);
file->write(_inventoryFlags, NUM_INVENTORY_ITEMS);
file->writeByte(_inventoryNumLeafs);
file->writeByte(_weapon ? 1 : 0);
file->writeByte(0);
return true;
}
void GameState::setChapter(int16 chapter) {
if (_engine->isLBA1()) {
_gameChapter = chapter;
return;
}
setGameFlag(253, chapter);
}
int16 GameState::getChapter() const {
if (_engine->isLBA1()) {
return _gameChapter;
}
return _listFlagGame[253];
}
void GameState::setGameFlag(uint8 index, int16 value) {
if (_listFlagGame[index] == value) {
return;
}
debug(2, "Set gameStateFlags[%u]=%u", index, value);
_listFlagGame[index] = value;
if (!value) {
return;
}
if ((index == GAMEFLAG_VIDEO_BAFFE || index == GAMEFLAG_VIDEO_BAFFE2 || index == GAMEFLAG_VIDEO_BAFFE3 || index == GAMEFLAG_VIDEO_BAFFE5) &&
_listFlagGame[GAMEFLAG_VIDEO_BAFFE] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE2] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE3] != 0 && _listFlagGame[GAMEFLAG_VIDEO_BAFFE5] != 0) {
// all 4 slap videos
_engine->unlockAchievement("LBA_ACH_012");
} else if (index == GAMEFLAG_VIDEO_BATEAU2) {
// second video of ferry trip
_engine->unlockAchievement("LBA_ACH_010");
} else if (index == (uint8)InventoryItems::kiUseSabre) {
_engine->unlockAchievement("LBA_ACH_002");
} else if (index == (uint8)InventoryItems::kBottleOfSyrup) {
_engine->unlockAchievement("LBA_ACH_007");
}
}
void GameState::doFoundObj(InventoryItems item) {
_engine->_grid->centerOnActor(_engine->_scene->_sceneHero);
// Hide hero in scene
_engine->_scene->_sceneHero->_flags.bIsInvisible = 1;
_engine->_redraw->drawScene(true);
_engine->_scene->_sceneHero->_flags.bIsInvisible = 0;
_engine->saveFrontBuffer();
IVec3 itemCamera;
itemCamera.x = _engine->_grid->_startCube.x * SIZE_BRICK_XZ;
itemCamera.y = _engine->_grid->_startCube.y * SIZE_BRICK_Y;
itemCamera.z = _engine->_grid->_startCube.z * SIZE_BRICK_XZ;
BodyData &bodyData = _engine->_scene->_sceneHero->_entityDataPtr->getBody(_engine->_scene->_sceneHero->_body);
const IVec3 bodyPos = _engine->_scene->_sceneHero->_posObj - itemCamera;
Common::Rect modelRect;
_engine->_renderer->renderIsoModel(bodyPos, LBAAngles::ANGLE_0, LBAAngles::ANGLE_45, LBAAngles::ANGLE_0, bodyData, modelRect);
_engine->_interface->setClip(modelRect);
const int32 itemX = (_engine->_scene->_sceneHero->_posObj.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 itemY = _engine->_scene->_sceneHero->_posObj.y / SIZE_BRICK_Y;
if (_engine->_scene->_sceneHero->brickShape() != ShapeType::kNone) {
itemY++;
}
const int32 itemZ = (_engine->_scene->_sceneHero->_posObj.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(itemX, itemY, itemZ);
IVec3 projPos = _engine->_renderer->projectPoint(bodyPos);
projPos.y -= 150;
const int32 boxTopLeftX = projPos.x - (SIZE_FOUND_OBJ / 2);
const int32 boxTopLeftY = projPos.y - (SIZE_FOUND_OBJ / 2);
const int32 boxBottomRightX = projPos.x + (SIZE_FOUND_OBJ / 2);
const int32 boxBottomRightY = projPos.y + (SIZE_FOUND_OBJ / 2);
const Common::Rect boxRect(boxTopLeftX, boxTopLeftY, boxBottomRightX, boxBottomRightY);
_engine->_sound->mixSample(Samples::BigItemFound, 0x1000, 1, 128, 128);
// process vox play
_engine->_music->stopMusic();
_engine->_text->initDial(TextBankId::Inventory_Intro_and_Holomap);
_engine->_interface->unsetClip();
_engine->_text->initItemFoundText(item);
_engine->_text->initDialWindow();
ProgressiveTextState textState = ProgressiveTextState::ContinueRunning;
_engine->_text->initVoxToPlayTextId((TextId)item);
const int32 bodyAnimIdx = _engine->_animations->searchAnim(AnimationTypes::kFoundItem, OWN_ACTOR_SCENE_INDEX);
const AnimData &ptranim = _engine->_resources->_animData[bodyAnimIdx];
_engine->_animations->stockInterAnim(bodyData, &bodyData._animTimerData);
uint frameanim = 0;
_engine->_redraw->_nbOptPhysBox = 0;
AnimTimerDataStruct animTimerData;
ScopedKeyMap uiKeyMap(_engine, uiKeyMapId);
int16 itemAngle = LBAAngles::ANGLE_0;
for (;;) {
FrameMarker frame(_engine, 66);
_engine->_interface->unsetClip();
_engine->_redraw->_nbPhysBox = 0;
_engine->_redraw->clsBoxes();
_engine->_interface->shadeBox(boxRect, 4);
_engine->_interface->setClip(boxRect);
itemAngle += LBAAngles::ANGLE_2;
_engine->_renderer->draw3dObject(projPos.x, projPos.y, _engine->_resources->_inventoryTable[item], itemAngle, 10000);
_engine->_menu->drawRectBorders(boxRect);
_engine->_redraw->addPhysBox(boxRect);
_engine->_interface->unsetClip();
init3DGame();
if (_engine->_animations->setInterAnimObjet(frameanim, ptranim, bodyData, &animTimerData)) {
frameanim++; // keyframe
if (frameanim >= ptranim.getNbFramesAnim()) {
frameanim = ptranim.getLoopFrame();
}
}
_engine->_renderer->renderIsoModel(bodyPos, LBAAngles::ANGLE_0, LBAAngles::ANGLE_45, LBAAngles::ANGLE_0, bodyData, modelRect);
_engine->_interface->setClip(modelRect);
_engine->_grid->drawOverBrick(itemX, itemY, itemZ);
_engine->_redraw->addPhysBox(modelRect);
if (textState == ProgressiveTextState::ContinueRunning) {
_engine->_interface->unsetClip();
textState = _engine->_text->nextDialChar();
} else {
_engine->_text->fadeInRemainingChars();
}
_engine->_redraw->flipBoxes();
_engine->readKeys();
if (_engine->_input->toggleAbortAction()) {
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
break;
}
if (_engine->_input->toggleActionIfActive(TwinEActionType::UINextPage)) {
if (textState == ProgressiveTextState::End) {
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
break;
}
if (textState == ProgressiveTextState::NextPage) {
textState = ProgressiveTextState::ContinueRunning;
}
}
_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry);
// advance the timer to play animations
_engine->timerRef++;
debugC(3, kDebugLevels::kDebugTimers, "FoundObj time: %i", _engine->timerRef);
}
while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
FrameMarker frame(_engine);
_engine->readKeys();
if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
break;
}
}
init3DGame();
_engine->_text->initSceneTextBank();
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
}
void GameState::gameAskChoice(TextId choiceIdx) {
_engine->saveFrontBuffer();
_gameChoicesSettings.reset();
_gameChoicesSettings.setTextBankId((TextBankId)((int)_engine->_scene->_sceneTextBank + (int)TextBankId::Citadel_Island));
// filled via script
for (int32 i = 0; i < _gameNbChoices; i++) {
_gameChoicesSettings.addButton(_gameListChoice[i], 0);
}
_engine->_text->drawAskQuestion(choiceIdx);
_engine->_menu->doGameMenu(&_gameChoicesSettings);
const int16 activeButton = _gameChoicesSettings.getActiveButton();
_gameChoice = _gameListChoice[activeButton];
// get right VOX entry index
if (_engine->_text->initVoxToPlayTextId(_gameChoice)) {
while (_engine->_text->playVoxSimple(_engine->_text->_currDialTextEntry)) {
FrameMarker frame(_engine);
if (_engine->shouldQuit()) {
break;
}
}
_engine->_text->stopVox(_engine->_text->_currDialTextEntry);
_engine->_text->_hasHiddenVox = false;
_engine->_text->_voxHiddenIndex = 0;
}
}
void GameState::processGameoverAnimation() {
const int32 tmpLbaTime = _engine->timerRef;
_engine->testRestoreModeSVGA(false);
// workaround to fix hero redraw after drowning
_engine->_scene->_sceneHero->_flags.bIsInvisible = 1;
_engine->_redraw->drawScene(true);
_engine->_scene->_sceneHero->_flags.bIsInvisible = 0;
// TODO: inSceneryView
_engine->setPalette(_engine->_screens->_ptrPal);
_engine->saveFrontBuffer();
BodyData gameOverPtr;
if (!gameOverPtr.loadFromHQR(Resources::HQR_RESS_FILE, RESSHQR_GAMEOVERMDL, _engine->isLBA1())) {
return;
}
_engine->_sound->stopSamples();
_engine->_music->stopMusicMidi(); // stop fade music
_engine->_renderer->setProjection(_engine->width() / 2, _engine->height() / 2, 128, 200, 200);
int32 startLbaTime = _engine->timerRef;
const Common::Rect &rect = _engine->centerOnScreen(_engine->width() / 2, _engine->height() / 2);
_engine->_interface->setClip(rect);
int32 zoom = 50000;
Common::Rect dummy;
while (!_engine->_input->toggleAbortAction() && (_engine->timerRef - startLbaTime) <= _engine->toSeconds(10)) {
FrameMarker frame(_engine, 66);
_engine->readKeys();
if (_engine->shouldQuit()) {
return;
}
zoom = boundRuleThree(40000, 3200, _engine->toSeconds(10), _engine->timerRef - startLbaTime);
const int32 angle = ruleThree32(1, LBAAngles::ANGLE_360, _engine->toSeconds(2), (_engine->timerRef - startLbaTime) % _engine->toSeconds(2));
_engine->blitWorkToFront(rect);
_engine->_renderer->setFollowCamera(0, 0, 0, 0, -angle, 0, zoom);
_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, gameOverPtr, dummy);
_engine->timerRef++;
debugC(3, kDebugLevels::kDebugTimers, "GameOver time: %i", _engine->timerRef);
}
const uint16 pitchBend = 0x1000 + _engine->getRandomNumber(2000) - (2000 / 2);
_engine->_sound->mixSample(Samples::Explode, pitchBend, 1, 128, 128);
_engine->blitWorkToFront(rect);
_engine->_renderer->setFollowCamera(0, 0, 0, 0, 0, 0, zoom);
_engine->_renderer->affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, LBAAngles::ANGLE_0, gameOverPtr, dummy);
_engine->delaySkip(2000);
_engine->_interface->unsetClip();
_engine->restoreFrontBuffer();
init3DGame();
_engine->timerRef = tmpLbaTime;
}
void GameState::giveUp() {
_engine->_sound->stopSamples();
// TODO: is an autosave desired on giving up? I don't think so. What did the original game do here?
//_engine->autoSave();
initGameStateVars();
_engine->_scene->stopRunningGame();
}
int16 GameState::setGas(int16 value) {
_inventoryNumGas = CLIP<int16>(value, 0, 100);
return _inventoryNumGas;
}
void GameState::addGas(int16 value) {
setGas(_inventoryNumGas + value);
}
// late game items from lba1 classic new game +
void GameState::handleLateGameItems() {
if (!_endGameItems) {
return;
}
debug("Give end game items");
_endGameItems = false;
_magicLevelIdx = 4;
setMaxMagicPoints();
giveItem(InventoryItems::kiUseSabre);
giveItem(InventoryItems::kiProtoPack);
giveItem(InventoryItems::kiHolomap);
giveItem(InventoryItems::kiTunic);
giveItem(InventoryItems::kiMagicBall);
giveItem(InventoryItems::kSendellsMedallion);
giveItem(InventoryItems::kiPenguin);
giveItem(InventoryItems::kGasItem);
giveItem(InventoryItems::kiCloverLeaf);
addGas(10);
}
int16 GameState::setKashes(int16 value) {
_goldPieces = CLIP<int16>(value, 0, 999);
if (_engine->_gameState->_goldPieces >= 500) {
_engine->unlockAchievement("LBA_ACH_011");
}
return _goldPieces;
}
int16 GameState::setZlitos(int16 value) {
_zlitosPieces = CLIP<int16>(value, 0, 999);
return _zlitosPieces;
}
int16 GameState::setKeys(int16 value) {
_nbLittleKeys = MAX<int16>(0, value);
return _nbLittleKeys;
}
void GameState::addKeys(int16 val) {
setKeys(_nbLittleKeys + val);
}
void GameState::addKashes(int16 val) {
setKashes(_goldPieces + val);
}
int16 GameState::setMagicPoints(int16 val) {
_magicPoint = val;
if (_magicPoint > _magicLevelIdx * 20) {
_magicPoint = _magicLevelIdx * 20;
} else if (_magicPoint < 0) {
_magicPoint = 0;
}
return _magicPoint;
}
int16 GameState::setMaxMagicPoints() {
_magicPoint = _magicLevelIdx * 20;
return _magicPoint;
}
void GameState::addMagicPoints(int16 val) {
setMagicPoints(_magicPoint + val);
}
int16 GameState::setLeafs(int16 val) {
_inventoryNumLeafs = val;
if (_inventoryNumLeafs > _inventoryNumLeafsBox) {
_inventoryNumLeafs = _inventoryNumLeafsBox;
}
return _inventoryNumLeafs;
}
void GameState::addLeafs(int16 val) {
setLeafs(_inventoryNumLeafs + val);
}
int16 GameState::setLeafBoxes(int16 val) {
_inventoryNumLeafsBox = val;
if (_inventoryNumLeafsBox > 10) {
_inventoryNumLeafsBox = 10;
}
if (_inventoryNumLeafsBox == 5) {
_engine->unlockAchievement("LBA_ACH_003");
}
return _inventoryNumLeafsBox;
}
void GameState::addLeafBoxes(int16 val) {
setLeafBoxes(_inventoryNumLeafsBox + val);
}
void GameState::clearGameFlags() {
debug(2, "Clear all gameStateFlags");
Common::fill(&_listFlagGame[0], &_listFlagGame[NUM_GAME_FLAGS], 0);
}
int16 GameState::hasGameFlag(uint8 index) const {
debug(6, "Query gameStateFlags[%u]=%u", index, _listFlagGame[index]);
return _listFlagGame[index];
}
} // namespace TwinE

View File

@@ -0,0 +1,219 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_GAMESTATE_H
#define TWINE_SCENE_GAMESTATE_H
#include "common/scummsys.h"
#include "twine/menu/menu.h"
#include "twine/shared.h"
namespace TwinE {
/** Magicball strength*/
enum MagicballStrengthType {
kNoBallStrength = 2,
kYellowBallStrength = 3,
kGreenBallStrength = 4,
kRedBallStrength = 6,
kFireBallStrength = 8
};
class TwinEEngine;
class GameState {
private:
TwinEEngine *_engine;
void initGameStateVars();
void initHeroVars();
MenuSettings _gameChoicesSettings;
/**
* LBA engine game flags to save quest states
*
* 0-27: inventory related
* 28-158: story related
* 159..199: unused
* 200-219: video related
* 220..255: unused
*
* 35: If 0, a zommed sequence of opening the ventilation shaft will be played when Twinsen escapes
* his house after arresting Zoe. Set to 1 after the sequence (also if Twinsen is killed during the arrest).
* 47: Value of 1 indicates that Twinsen has opened the door to the Citadel Island Tavern's basement.
* The door will be always open from now on.
* 70: Set to 1 if inventory items are taken from Twinsen when he goes to jail (inventory is empty),
* set to 0 after he gets back his stuff.
* 92: Set to 1 if the green grobo in the Citadel Island Tavern has told Twinsen about taking Zoe to the
* port and leaving for another island.
* 107: Set to 1 after Twinsen kills yellow groboclone in the Citadel Island Tavern (after the Tavern has
* been closed down). Makes the Tavern open again and groboclone not appear any more.
*/
int16 _listFlagGame[NUM_GAME_FLAGS]; // ListVarGame
// only lba1 - lba2 uses 253 gameflag
int16 _gameChapter = 0;
public:
GameState(TwinEEngine *engine);
/**
* LBA engine chapter
* 0: Imprisoned
* 1: Escape from the citadel
* 2: Zoe got captured
* 3: - looking for a young girl
* 4: - looking for a "friend"
* 5: The legend of Sendell
* 6: The book of Bu
* 7: Pirate LeBorne
* 8: - "good day"
* 9: - "good day"
* 10: - ?? nothing
* 11: - ?? nothing
* 12: - ?? nothing
* 13: - looking for plans
* 14: - still looking for plans
* 15: The final showdown - "good day"
*/
void setChapter(int16 chapter);
int16 getChapter() const;
/** Magic ball type index */
int16 _magicBall = 0;
/** Magic ball num bounce */
int16 _magicBallType = 0;
/** Magic ball auxiliar bounce number */
int16 _magicBallCount = 0; // magicBallParam
/** Magic level index */
int16 _magicLevelIdx = 0;
/** Store the number of inventory keys */
int16 _nbLittleKeys = 0;
/** Store the number of inventory kashes */
int16 _goldPieces = 0;
int16 _zlitosPieces = 0;
/** Store the number of inventory clover leafs boxes */
int16 _inventoryNumLeafsBox = 0;
/** Store the number of inventory clover leafs */
int16 _inventoryNumLeafs = 0;
/** Store the number of inventory magic points */
int16 _magicPoint = 0;
/** Store the number of gas */
int16 _inventoryNumGas = 0;
/** Its using FunFrock Sabre */
bool _weapon = false;
bool _endGameItems = false;
/**
* Inventory used flags
* 0 means never used, 1 means already used and automatic re-use
*/
uint8 _inventoryFlags[NUM_INVENTORY_ITEMS];
uint8 _holomapFlags[MAX_HOLO_POS_2];
char _sceneName[30] {};
TextId _gameListChoice[10]; // inGameMenuData
int32 _gameNbChoices = 0; // numOfOptionsInChoice
TextId _gameChoice = TextId::kNone; // inGameMenuAnswer
void setDarts(int16 value) {
setGameFlag(InventoryItems::kiDart, value);
}
void addDart() {
int16 old = _listFlagGame[InventoryItems::kiDart];
++old;
setGameFlag(InventoryItems::kiDart, old);
}
inline bool inventoryDisabled() const {
return hasGameFlag(GAMEFLAG_INVENTORY_DISABLED) != 0;
}
inline bool hasOpenedFunfrocksSafe() const {
return hasGameFlag(30) != 0;
}
// Arrived on the hamalayi with the rebels
inline bool hasArrivedHamalayi() const {
return hasGameFlag(90) != 0;
}
inline bool hasItem(InventoryItems item) const {
return hasGameFlag(item) != 0;
}
inline void giveItem(InventoryItems item) {
setGameFlag(item, 1);
}
inline void removeItem(InventoryItems item) {
setGameFlag(item, 0);
}
void clearGameFlags();
int16 hasGameFlag(uint8 index) const;
void setGameFlag(uint8 index, int16 value);
int16 setKeys(int16 value);
int16 setGas(int16 value);
int16 setLeafs(int16 value);
int16 setKashes(int16 value);
int16 setZlitos(int16 value);
int16 setMagicPoints(int16 val);
int16 setMaxMagicPoints();
int16 setLeafBoxes(int16 val);
void handleLateGameItems();
void addGas(int16 value);
void addKeys(int16 val);
void addKashes(int16 val);
void addMagicPoints(int16 val);
void addLeafs(int16 val);
void addLeafBoxes(int16 val);
/** Initialize all engine variables */
void initEngineVars();
/** Initialize engine 3D projections */
void init3DGame();
void doFoundObj(InventoryItems item);
void giveUp();
bool loadGame(Common::SeekableReadStream *file);
bool saveGame(Common::WriteStream *file);
void gameAskChoice(TextId choiceIdx);
void processGameoverAnimation();
};
} // namespace TwinE
#endif

115
engines/twine/scene/graph.h Normal file
View File

@@ -0,0 +1,115 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_GRAPH_H
#define TWINE_SCENE_GRAPH_H
#include "graphics/screen.h"
#include "twine/shared.h"
namespace TwinE {
// WARNING: Rewrite this function to have better performance
inline bool drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite, Graphics::Screen &frontVideoBuffer, const Common::Rect &clip) { // AffGraph
if (!clip.isValidRect()) {
return false;
}
const int32 left = posX + pGraph[2];
if (left >= clip.right) {
return false;
}
const int32 top = posY + pGraph[3];
if (top >= clip.bottom) {
return false;
}
const int32 right = pGraph[0] + left;
if (right < clip.left) {
return false;
}
const int32 bottom = pGraph[1] + top;
if (bottom < clip.top) {
return false;
}
const int32 maxY = MIN(bottom, (int32)clip.bottom);
pGraph += 4; // skip the header
int32 x = left;
// if (left >= textWindowLeft-2 && top >= textWindowTop-2 && right <= textWindowRight-2 && bottom <= textWindowBottom-2) // crop
{
for (int32 y = top; y < maxY; ++y) {
const uint8 rleAmount = *pGraph++;
for (int32 run = 0; run < rleAmount; ++run) {
const uint8 rleMask = *pGraph++;
const uint8 iterations = bits(rleMask, 0, 6) + 1;
const uint8 opCode = bits(rleMask, 6, 2);
if (opCode == 0) {
x += iterations;
continue;
}
if (y < clip.top || x >= clip.right || x + iterations < clip.left) {
if (opCode == 1) {
pGraph += iterations;
} else {
++pGraph;
}
x += iterations;
continue;
}
if (opCode == 1) { // 0x40
uint8 *out = (uint8 *)frontVideoBuffer.getBasePtr(x, y);
for (uint8 i = 0; i < iterations; i++) {
if (x >= clip.left && x < clip.right) {
*out = *pGraph;
}
++out;
++x;
++pGraph;
}
} else {
const uint8 pixel = *pGraph++;
uint8 *out = (uint8 *)frontVideoBuffer.getBasePtr(x, y);
for (uint8 i = 0; i < iterations; i++) {
if (x >= clip.left && x < clip.right) {
*out = pixel;
}
++out;
++x;
}
}
}
x = left;
}
}
Common::Rect rect(left, top, right, bottom);
frontVideoBuffer.addDirtyRect(rect);
return true;
}
} // namespace TwinE
#endif // TWINE_SCENE_GRAPH_H

View File

@@ -0,0 +1,798 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "twine/debugger/debug_state.h"
#include "twine/menu/interface.h"
#include "twine/parser/blocklibrary.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/collision.h"
#include "twine/scene/graph.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/twine.h"
#define CELLING_GRIDS_START_INDEX 120
namespace TwinE {
Grid::Grid(TwinEEngine *engine) : _engine(engine) {
_blockBufferSize = SIZE_CUBE_X * SIZE_CUBE_Z * SIZE_CUBE_Y * sizeof(BlockEntry);
_bufCube = (uint8 *)malloc(_blockBufferSize);
}
Grid::~Grid() {
free(_bufCube);
for (int32 i = 0; i < ARRAYSIZE(_brickMaskTable); i++) {
free(_brickMaskTable[i]);
}
for (int32 i = 0; i < ARRAYSIZE(_bufferBrick); i++) {
free(_bufferBrick[i]);
}
free(_currentGrid);
free(_nbBrickColon);
free(_listBrickColon);
}
void Grid::init(int32 w, int32 h) {
const int32 numbrickentries = (1 + (w + 24) / 24);
const size_t brickDataBufferSize = numbrickentries * MAX_BRICKS * sizeof(BrickEntry);
_listBrickColon = (BrickEntry *)malloc(brickDataBufferSize);
_brickInfoBufferSize = numbrickentries * sizeof(int16);
_nbBrickColon = (int16 *)malloc(_brickInfoBufferSize);
}
void Grid::copyMask(int32 index, int32 x, int32 y, const Graphics::ManagedSurface &buffer) {
if (_engine->_debugState->_disableGridRendering) {
return;
}
uint8 *ptr = _brickMaskTable[index];
int32 left = x + *(ptr + 2);
int32 top = y + *(ptr + 3);
int32 right = *ptr + left - 1;
int32 bottom = *(ptr + 1) + top - 1;
if (left > _engine->_interface->_clip.right || right < _engine->_interface->_clip.left || bottom < _engine->_interface->_clip.top || top > _engine->_interface->_clip.bottom) {
return;
}
ptr += 4;
int32 absX = left;
int32 absY = top;
int32 vSize = (bottom - top) + 1;
if (vSize <= 0) {
return;
}
int32 offset = -((right - left) - _engine->width()) - 1;
right++;
bottom++;
// if line on top aren't in the blitting area...
if (absY < _engine->_interface->_clip.top) {
int numOfLineToRemove = _engine->_interface->_clip.top - absY;
vSize -= numOfLineToRemove;
if (vSize <= 0) {
return;
}
absY += numOfLineToRemove;
do {
int lineDataSize;
lineDataSize = *(ptr++);
ptr += lineDataSize;
} while (--numOfLineToRemove);
}
// reduce the vSize to remove lines on bottom
if (absY + vSize - 1 > _engine->_interface->_clip.bottom) {
vSize = _engine->_interface->_clip.bottom - absY + 1;
if (vSize <= 0) {
return;
}
}
uint8 *outPtr = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(left, absY);
const uint8 *inPtr = (const uint8 *)buffer.getBasePtr(left, absY);
do {
int32 height = *(ptr++);
do {
int32 width = *(ptr++); // skip size
outPtr += width;
inPtr += width;
absX += width;
height--;
if (!height) {
break;
}
width = *(ptr++); // copy size
for (int32 j = 0; j < width; j++) {
if (absX >= _engine->_interface->_clip.left && absX <= _engine->_interface->_clip.right) {
*outPtr = *inPtr;
}
absX++;
outPtr++;
inPtr++;
}
} while (--height);
absX = left;
outPtr += offset;
inPtr += offset;
} while (--vSize);
}
const BrickEntry *Grid::getBrickEntry(int32 j, int32 i) const {
return &_listBrickColon[j * MAX_BRICKS + i];
}
void Grid::drawOverBrick(int32 x, int32 y, int32 z) {
const int32 startCol = ((_engine->_interface->_clip.left + 24) / 24) - 1;
const int32 endCol = ((_engine->_interface->_clip.right + 24) / 24);
for (int32 col = startCol; col <= endCol; col++) {
for (int32 i = 0; i < _nbBrickColon[col]; i++) {
const BrickEntry *currBrickEntry = getBrickEntry(col, i);
if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
if (currBrickEntry->x + currBrickEntry->z > z + x) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
}
}
}
}
void Grid::drawOverBrick3(int32 x, int32 y, int32 z) {
const int32 startCol = ((_engine->_interface->_clip.left + 24) / 24) - 1;
const int32 endCol = (_engine->_interface->_clip.right + 24) / 24;
for (int32 col = startCol; col <= endCol; col++) {
for (int32 i = 0; i < _nbBrickColon[col]; i++) {
const BrickEntry *currBrickEntry = getBrickEntry(col, i);
if (currBrickEntry->posY + 38 > _engine->_interface->_clip.top && currBrickEntry->posY <= _engine->_interface->_clip.bottom && currBrickEntry->y >= y) {
if (currBrickEntry->x == x && currBrickEntry->z == z) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
if (currBrickEntry->x > x || currBrickEntry->z > z) {
copyMask(currBrickEntry->index, (col * 24) - 24, currBrickEntry->posY, _engine->_workVideoBuffer);
}
}
}
}
}
void Grid::processGridMask(const uint8 *buffer, uint8 *ptr) {
const uint8 width = *buffer++;
uint8 height = *buffer++;
const uint8 offsetX = *buffer++;
const uint8 offsetY = *buffer++;
const int32 maxY = offsetY + height;
*ptr++ = width;
*ptr++ = height;
*ptr++ = offsetX;
*ptr++ = offsetY;
uint8 *targetPtrPos = ptr;
for (int32 y = offsetY; y < maxY; ++y) {
uint8 numOfBlock = 0;
uint8 opaquePixels = 0;
uint8 *numOfBlockTargetPtr = targetPtrPos;
targetPtrPos++;
const uint8 numRuns = *buffer++;
// the first time isn't skip. the skip size is 0 in that case
if (bits(*buffer, 6, 2) != 0) {
*targetPtrPos++ = 0;
numOfBlock++;
}
for (uint8 run = 0; run < numRuns; ++run) {
const uint8 runSpec = *buffer++;
const uint8 runLength = bits(runSpec, 0, 6) + 1;
const uint8 type = bits(runSpec, 6, 2);
if (type == 2) {
opaquePixels += runLength;
buffer++;
} else if (type == 1) { // 0x40
opaquePixels += runLength;
buffer += runLength;
} else { // skip (type 3)
if (opaquePixels) {
*targetPtrPos++ = opaquePixels; // write down the number of pixel passed so far
numOfBlock++;
opaquePixels = 0;
}
*targetPtrPos++ = runLength; // write skip
numOfBlock++;
}
}
if (opaquePixels) {
*targetPtrPos++ = opaquePixels;
numOfBlock++;
opaquePixels = 0;
}
*numOfBlockTargetPtr = numOfBlock;
}
}
void Grid::createGridMask() {
for (int32 b = 0; b < NUM_BRICKS; b++) {
if (!_brickUsageTable[b]) {
continue;
}
if (_brickMaskTable[b]) {
free(_brickMaskTable[b]);
}
_brickMaskTable[b] = (uint8 *)malloc(_brickSizeTable[b]);
processGridMask(_bufferBrick[b], _brickMaskTable[b]);
}
}
void Grid::getSpriteSize(int32 offset, int32 *width, int32 *height, const uint8 *spritePtr) {
spritePtr += READ_LE_INT32(spritePtr + offset * 4);
*width = *spritePtr;
*height = *(spritePtr + 1);
}
void Grid::loadGridBricks() {
uint32 firstBrick = 60000;
uint32 lastBrick = 0;
uint32 currentBllEntryIdx = 1;
memset(_brickSizeTable, 0, sizeof(_brickSizeTable));
memset(_brickUsageTable, 0, sizeof(_brickUsageTable));
// get block libraries usage bits
const uint8 *ptrToBllBits = _currentGrid + (_currentGridSize - 32);
// for all bits under the 32bytes (256bits)
for (uint32 i = 1; i < 256; i++) {
const uint8 currentBitByte = *(ptrToBllBits + (i / 8));
const uint8 currentBitMask = 1 << (7 - (i & 7));
if (currentBitByte & currentBitMask) {
const BlockData *currentBllPtr = getBlockLibrary(currentBllEntryIdx);
for (const BlockDataEntry &entry : currentBllPtr->entries) {
uint16 brickIdx = entry.brickIdx;
if (!brickIdx) {
continue;
}
brickIdx--;
if (brickIdx <= firstBrick) {
firstBrick = brickIdx;
}
if (brickIdx > lastBrick) {
lastBrick = brickIdx;
}
_brickUsageTable[brickIdx] = 1;
}
}
++currentBllEntryIdx;
}
for (uint32 i = firstBrick; i <= lastBrick; i++) {
if (!_brickUsageTable[i]) {
free(_bufferBrick[i]);
_bufferBrick[i] = nullptr;
continue;
}
_brickSizeTable[i] = HQR::getAllocEntry(&_bufferBrick[i], Resources::HQR_LBA_BRK_FILE, i);
if (_brickSizeTable[i] == 0) {
warning("Failed to load isometric brick index %i", i);
}
}
}
void Grid::decompColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) { // DecompColonne
Common::MemoryReadStream stream(gridEntry, gridEntrySize);
Common::MemoryWriteStream outstream(dest, destSize);
int32 brickCount = stream.readByte();
do {
const int32 flag = stream.readByte();
const int32 blockCount = bits(flag, 0, 6) + 1;
const int32 type = bits(flag, 6, 2);
if (type == 0) {
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(0);
}
} else if (type == 1) { // 0x40
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(stream.readUint16LE());
}
} else {
const uint16 gridIdx = stream.readUint16LE();
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(gridIdx);
}
}
assert(!outstream.err());
assert(!stream.err());
} while (--brickCount);
}
void Grid::calcGraphMsk(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize) {
Common::MemoryReadStream stream(gridEntry, gridEntrySize);
Common::SeekableMemoryWriteStream outstream(dest, destSize);
int32 brickCount = stream.readByte();
do {
const int32 flag = stream.readByte();
const int32 blockCount = bits(flag, 0, 6) + 1;
const int32 type = bits(flag, 6, 2);
if (type == 0) {
for (int32 i = 0; i < blockCount; i++) {
outstream.seek(outstream.pos() + 2);
}
} else if (type == 1) {
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(stream.readUint16LE());
}
} else {
const uint16 gridIdx = stream.readUint16LE();
for (int32 i = 0; i < blockCount; i++) {
outstream.writeUint16LE(gridIdx);
}
}
assert(!outstream.err());
assert(!stream.err());
} while (--brickCount);
}
void Grid::copyMapToCube() {
int32 blockOffset = 0;
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
const int32 gridIdx = z * SIZE_CUBE_X;
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
const int32 gridOffset = READ_LE_UINT16(_currentGrid + 2 * (x + gridIdx));
decompColumn(_currentGrid + gridOffset, _currentGridSize - gridOffset, _bufCube + blockOffset, _blockBufferSize - blockOffset);
blockOffset += 2 * SIZE_CUBE_Y;
}
}
}
void Grid::createCellingGridMap(const uint8 *gridPtr, int32 gridPtrSize) { // MixteMapToCube
int32 currGridOffset = 0;
int32 blockOffset = 0;
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
const uint8 *tempGridPtr = gridPtr + currGridOffset;
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
const int gridOffset = READ_LE_UINT16(tempGridPtr);
tempGridPtr += 2;
calcGraphMsk(gridPtr + gridOffset, gridPtrSize - gridOffset, _bufCube + blockOffset, _blockBufferSize - blockOffset);
blockOffset += 2 * SIZE_CUBE_Y;
}
currGridOffset += SIZE_CUBE_X + SIZE_CUBE_Z;
}
}
bool Grid::initGrid(int32 index) {
// load grids from file
_currentGridSize = HQR::getAllocEntry(&_currentGrid, Resources::HQR_LBA_GRI_FILE, index);
if (_currentGridSize == 0) {
warning("Failed to load grid index: %i", index);
return false;
}
// load layouts from file
if (!_currentBlockLibrary.loadFromHQR(Resources::HQR_LBA_BLL_FILE, index, _engine->isLBA1())) {
warning("Failed to load block library index: %i", index);
return false;
}
loadGridBricks();
createGridMask();
copyMapToCube();
return true;
}
bool Grid::initCellingGrid(int32 index) { // IncrustGrm
uint8 *gridPtr = nullptr;
// load grids from file
const int realIndex = index + CELLING_GRIDS_START_INDEX;
const int32 gridSize = HQR::getAllocEntry(&gridPtr, Resources::HQR_LBA_GRI_FILE, realIndex);
if (gridSize == 0) {
warning("Failed to load grid index %i", realIndex);
return false;
}
createCellingGridMap(gridPtr, gridSize);
free(gridPtr);
_engine->_redraw->_firstTime = true;
return true;
}
bool Grid::drawGraph(int32 index, int32 posX, int32 posY) { // AffGraph
return drawGraph(posX, posY, _bufferBrick[index], false);
}
bool Grid::drawGraph(int32 index, int32 posX, int32 posY, const uint8 *ptr) { // AffGraph
ptr = ptr + READ_LE_INT32(ptr + index * 4); // GetGraph, GetMask
return drawGraph(posX, posY, ptr, true);
}
bool Grid::drawSprite(int32 posX, int32 posY, const SpriteData &ptr, int spriteIndex) {
const int32 left = posX + ptr.offsetX(spriteIndex);
if (left >= _engine->_interface->_clip.right) {
return false;
}
const int32 right = ptr.surface(spriteIndex).w + left;
if (right < _engine->_interface->_clip.left) {
return false;
}
const int32 top = posY + ptr.offsetY(spriteIndex);
if (top >= _engine->_interface->_clip.bottom) {
return false;
}
const int32 bottom = ptr.surface(spriteIndex).h + top;
if (bottom < _engine->_interface->_clip.top) {
return false;
}
const Common::Point pos(left, top);
_engine->_frontVideoBuffer.transBlitFrom(ptr.surface(spriteIndex), pos);
return true;
}
bool Grid::drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite) {
if (_engine->_debugState->_disableGridRendering) {
return false;
}
const Common::Rect &clip = _engine->_interface->_clip;
TwineScreen &frontVideoBuffer = _engine->_frontVideoBuffer;
return TwinE::drawGraph(posX, posY, pGraph, isSprite, frontVideoBuffer, clip);
}
const uint8 *Grid::getBlockBufferGround(const IVec3 &pos, int32 &ground) {
const IVec3 &collision = updateCollisionCoordinates(pos.x, pos.y, pos.z);
const uint8 *ptr = _bufCube + collision.y * 2 + collision.x * SIZE_CUBE_Y * 2 + collision.z * SIZE_CUBE_X * SIZE_CUBE_Y * 2;
int32 collisionY = collision.y;
while (collisionY) {
if (READ_LE_INT16(ptr)) { // found the ground - sizeof(BlockEntry);
break;
}
collisionY--;
ptr -= sizeof(int16);
}
_engine->_collision->_collision.y = collisionY;
ground = (int16)((collisionY + 1) * SIZE_BRICK_Y);
return ptr;
}
const BlockDataEntry *Grid::getAdrBlock(int32 blockIdx, int32 brickIdx) const {
const BlockData *blockPtr = getBlockLibrary(blockIdx);
return &blockPtr->entries[brickIdx];
}
const BlockData *Grid::getBlockLibrary(int32 blockIdx) const {
return _currentBlockLibrary.getLayout(blockIdx - 1);
}
void Grid::map2Screen(int32 x, int32 y, int32 z, int32 &posx, int32 &posy) const {
posx = (x - z) * 24 + _engine->width() / 2 - SIZE_CUBE_X / 2;
posy = ((x + z) * 12) - (y * 15) + _engine->height() / 2 - SIZE_CUBE_Y;
}
void Grid::drawBrickBlock(int32 blockIdx, int32 brickBlockIdx, int32 x, int32 y, int32 z) { // AffBrickBlock
const BlockDataEntry *blockPtr = getAdrBlock(blockIdx, brickBlockIdx);
const uint8 brickShape = blockPtr->brickShape;
const uint8 brickSound = blockPtr->brickType;
const uint16 numBrick = blockPtr->brickIdx;
if (!numBrick) {
return;
}
int32 brickPixelPosX = 0;
int32 brickPixelPosY = 0;
map2Screen(x - _startCube.x, y - _startCube.y, z - _startCube.z, brickPixelPosX, brickPixelPosY);
if (brickPixelPosX < -24) {
return;
}
if (brickPixelPosX >= _engine->width()) {
return;
}
if (brickPixelPosY < -38) {
return;
}
if (brickPixelPosY >= _engine->height()) {
return;
}
// draw the background brick
drawGraph(numBrick - 1, brickPixelPosX, brickPixelPosY);
int32 col = (brickPixelPosX + 24) / 24;
if (_nbBrickColon[col] >= MAX_BRICKS) {
warning("GRID: brick buffer exceeded");
return;
}
BrickEntry *pColonBrick = &_listBrickColon[col * MAX_BRICKS + _nbBrickColon[col]];
pColonBrick->x = x;
pColonBrick->y = y;
pColonBrick->z = z;
pColonBrick->posX = brickPixelPosX;
pColonBrick->posY = brickPixelPosY;
pColonBrick->index = numBrick - 1;
pColonBrick->shape = brickShape;
pColonBrick->sound = brickSound;
_nbBrickColon[col]++;
}
void Grid::redrawGrid() { // AffGrille
_worldCube.x = _startCube.x * SIZE_BRICK_XZ;
_worldCube.y = _startCube.y * SIZE_BRICK_Y;
_worldCube.z = _startCube.z * SIZE_BRICK_XZ;
const IVec3 &projPos = _engine->_renderer->projectPoint(-_worldCube);
_engine->_redraw->_projPosScreen.x = projPos.x;
_engine->_redraw->_projPosScreen.y = projPos.y;
memset(_nbBrickColon, 0, _brickInfoBufferSize);
if (!_engine->_scene->_flagRenderGrid) {
return;
}
_engine->_screens->clearScreen();
for (int32 z = 0; z < SIZE_CUBE_Z; z++) {
for (int32 x = 0; x < SIZE_CUBE_X; x++) {
for (int32 y = 0; y < SIZE_CUBE_Y; y++) {
const BlockEntry entry = getBlockEntry(x, y, z);
if (entry.blockIdx) {
drawBrickBlock(entry.blockIdx, entry.brickBlockIdx, x, y, z);
}
}
}
}
}
BlockEntry Grid::getBlockEntry(int32 xmap, int32 ymap, int32 zmap) const {
const uint8 *pCube = _bufCube;
const int32 size = 2; // sizeof(BlockEntry);
pCube += xmap * SIZE_CUBE_Y * size;
pCube += ymap * size;
pCube += zmap * (SIZE_CUBE_X * SIZE_CUBE_Y * size);
BlockEntry entry;
entry.blockIdx = *pCube;
entry.brickBlockIdx = *(pCube + 1);
return entry;
}
ShapeType Grid::worldColBrick(int32 x, int32 y, int32 z) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
if (collision.y <= -1) {
return ShapeType::kSolid;
}
if (collision.x < 0 || collision.x >= SIZE_CUBE_X) {
return ShapeType::kNone;
}
if (collision.y < 0 || collision.y >= SIZE_CUBE_Y) {
return ShapeType::kNone;
}
if (collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
return ShapeType::kNone;
}
const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
if (entry.blockIdx) {
const BlockDataEntry *blockPtr = getAdrBlock(entry.blockIdx, entry.brickBlockIdx);
return (ShapeType)blockPtr->brickShape;
}
return (ShapeType)entry.brickBlockIdx; // eventually transparent color
}
const IVec3 &Grid::updateCollisionCoordinates(int32 x, int32 y, int32 z) {
_engine->_collision->_collision.x = (x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_collision->_collision.y = y / SIZE_BRICK_Y;
_engine->_collision->_collision.z = (z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
return _engine->_collision->_collision;
}
bool Grid::shouldCheckWaterCol(int32 actorIdx) const {
if (actorIdx == OWN_ACTOR_SCENE_INDEX) {
ActorStruct *ptrobj = _engine->_scene->getActor(actorIdx);
if (_engine->_actor->_heroBehaviour != HeroBehaviourType::kProtoPack
&& ptrobj->_flags.bComputeCollisionWithFloor
&& !ptrobj->_flags.bIsInvisible
&& !ptrobj->_workFlags.bIsFalling
&& ptrobj->_carryBy == -1) {
return true;
}
}
return false;
}
ShapeType Grid::worldColBrickFull(int32 x, int32 y, int32 z, int32 y2, int32 actorIdx) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
if (collision.y <= -1) {
return ShapeType::kSolid;
}
if (collision.x < 0 || collision.x >= SIZE_CUBE_X || collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
return ShapeType::kNone;
}
bool checkWater = shouldCheckWaterCol(actorIdx);
uint8 *pCube = _bufCube;
pCube += collision.x * SIZE_CUBE_Y * 2;
pCube += collision.y * 2;
pCube += collision.z * (SIZE_CUBE_X * SIZE_CUBE_Y * 2);
uint8 block = *pCube;
ShapeType brickShape;
const uint8 tmpBrickIdx = *(pCube + 1);
if (block) {
const BlockDataEntry *blockPtr = getAdrBlock(block, tmpBrickIdx);
if (checkWater && blockPtr->brickType == WATER_BRICK) {
brickShape = ShapeType::kSolid; // full collision
} else {
brickShape = (ShapeType)blockPtr->brickShape;
}
} else {
brickShape = (ShapeType)tmpBrickIdx; // maybe transparency
if (checkWater) {
uint8 *pCode = pCube;
for (y = collision.y - 1; y >= 0; y--) {
pCode -= 2;
uint8 code = *pCode;
if (code) {
const BlockDataEntry *blockPtr = getAdrBlock(block, 0);
if (blockPtr->brickType == WATER_BRICK) {
// Special check mount funfrock
if (_engine->_scene->_numCube != LBA1SceneId::Polar_Island_on_the_rocky_peak) {
// full collision
return ShapeType::kSolid;
}
}
break; // stop parsing at first encountered brick
}
}
}
}
int32 ymax = (y2 + (SIZE_BRICK_Y - 1)) / SIZE_BRICK_Y;
// check full height
for (y = collision.y; ymax > 0 && y < (SIZE_CUBE_Y - 1); --ymax, y++) {
pCube += 2;
if (READ_LE_INT16(pCube)) {
return ShapeType::kSolid;
}
}
return brickShape;
}
uint8 Grid::worldCodeBrick(int32 x, int32 y, int32 z) {
uint8 code = 0xF0U;
if (y > -1) {
const IVec3 &collision = updateCollisionCoordinates(x, y, z);
const BlockEntry entry = getBlockEntry(collision.x, collision.y, collision.z);
if (entry.blockIdx) {
const BlockDataEntry *blockPtr = getAdrBlock(entry.blockIdx, entry.brickBlockIdx);
code = blockPtr->brickType;
}
}
return code;
}
void Grid::centerOnActor(const ActorStruct *actor) {
_startCube.x = (actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_startCube.y = (actor->_posObj.y + SIZE_BRICK_Y) / SIZE_BRICK_Y;
_startCube.z = (actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_engine->_redraw->_firstTime = true;
}
void Grid::centerScreenOnActor() {
if (_engine->_cameraZone) {
return;
}
if (_engine->_debugState->_useFreeCamera) {
return;
}
ActorStruct *actor = _engine->_scene->getActor(_engine->_scene->_numObjFollow);
const IVec3 projPos = _engine->_renderer->projectPoint(actor->_posObj.x - (_startCube.x * SIZE_BRICK_XZ),
actor->_posObj.y - (_startCube.y * SIZE_BRICK_Y),
actor->_posObj.z - (_startCube.z * SIZE_BRICK_XZ));
// TODO: these border values should get scaled for higher resolutions
if (projPos.x < 80 || projPos.x >= _engine->width() - 60 || projPos.y < 80 || projPos.y >= _engine->height() - 50) {
_startCube.x = ((actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ) + (((actor->_posObj.x + SIZE_BRICK_Y) / SIZE_BRICK_XZ) - _startCube.x) / 2;
_startCube.y = actor->_posObj.y / SIZE_BRICK_Y;
_startCube.z = ((actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ) + (((actor->_posObj.z + SIZE_BRICK_Y) / SIZE_BRICK_XZ) - _startCube.z) / 2;
if (_startCube.x >= SIZE_CUBE_X) {
_startCube.x = SIZE_CUBE_X - 1;
}
if (_startCube.z >= SIZE_CUBE_Z) {
_startCube.z = SIZE_CUBE_Z - 1;
}
_engine->_redraw->_firstTime = true;
}
}
} // namespace TwinE

308
engines/twine/scene/grid.h Normal file
View File

@@ -0,0 +1,308 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_GRID_H
#define TWINE_SCENE_GRID_H
#define WATER_BRICK (0xF1) // CJ_WATER
#include "common/scummsys.h"
#include "twine/parser/blocklibrary.h"
#include "twine/parser/sprite.h"
#include "twine/shared.h"
namespace Graphics {
class ManagedSurface;
}
namespace TwinE {
class ActorStruct;
/** Block fragment entry */
struct BlockEntry {
/** Block library index */
uint8 blockIdx = 0;
/** Brick index inside the block library */
uint8 brickBlockIdx = 0;
};
/** Brick entry data */
struct BrickEntry { // T_COLONB
/** Brick X position in screen */
int16 x = 0; //z
/** Brick Y position in screen */
int16 y = 0;
/** Brick Z position in screen */
int16 z = 0; // x
/** Brick pixel X position */
int16 posX = 0;
/** Brick pixel Y position */
int16 posY = 0;
/** Brick index */
int16 index = 0;
/** Brick shape type */
uint8 shape = 0;
/** Brick sound type */
uint8 sound = 0;
};
/** Total number of bricks allowed in the game */
#define NUM_BRICKS 9000
/** Grid X size */
#define SIZE_CUBE_X 64
/** Grid Y size */
#define SIZE_CUBE_Y 25
/** Grid Z size */
#define SIZE_CUBE_Z SIZE_CUBE_X
#define ISO_SCALE 512
#define SIZE_BRICK_XZ 512
#define SIZE_BRICK_Y 256
#define DEMI_BRICK_XZ 256
#define DEMI_BRICK_Y 128
// short max 32767 0x7FFF
// 32256 0x7E00
// 32000 0x7D00
#define SCENE_SIZE_MAX (SIZE_BRICK_XZ * (SIZE_CUBE_X - 1))
// short min -32768
#define SCENE_SIZE_MIN (-SIZE_BRICK_XZ * SIZE_CUBE_X)
#define SCENE_SIZE_HALF (SIZE_BRICK_XZ * SIZE_CUBE_X / 2)
#define SCENE_SIZE_HALFF (SIZE_BRICK_XZ * SIZE_CUBE_X / 2.0f)
#define MAX_BRICKS 150
class TwinEEngine;
class Grid {
private:
TwinEEngine *_engine;
/**
* Draw a specific brick in the grid column according with the block index
* @param blockIdx block library index
* @param brickBlockIdx brick index inside the block
* @param x column x position
* @param y column y position
* @param z column z position
*/
void drawBrickBlock(int32 blockIdx, int32 brickBlockIdx, int32 x, int32 y, int32 z);
/**
* Get brick position in the screen
* @param x column x position in the current camera
* @param y column y position in the current camera
* @param z column z position in the current camera
*/
void map2Screen(int32 x, int32 y, int32 z, int32 &_brickPixelPosX, int32 &_brickPixelPosY) const;
/**
* Create celling grid map from celling grid to block library buffer
* @param gridPtr celling grid buffer pointer
*/
void createCellingGridMap(const uint8 *gridPtr, int32 gridPtrSize);
/**
* Create grid Y column in block buffer
* @param gridEntry current grid index
* @param dest destination block buffer
*/
void calcGraphMsk(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize);
/**
* Create grid Y column in block buffer
* @param gridEntry current grid index
* @param dest destination block buffer
*/
void decompColumn(const uint8 *gridEntry, uint32 gridEntrySize, uint8 *dest, uint32 destSize);
/**
* Load grid bricks according with block librarie usage
*/
void loadGridBricks();
/** Create grid masks to allow display actors over the bricks */
void createGridMask();
/**
* Process brick masks to allow actors to display over the bricks
* @param buffer brick pointer buffer
* @param ptr brick mask pointer buffer
*/
void processGridMask(const uint8 *buffer, uint8 *ptr);
/**
* Copy grid mask to allow actors to display over the bricks
* @param index current brick index
* @param x grid X coordinate
* @param y grid Y coordinate
* @param buffer work video buffer
*/
void copyMask(int32 index, int32 x, int32 y, const Graphics::ManagedSurface &buffer);
/** Table with all loaded bricks */
uint8 *_bufferBrick[NUM_BRICKS]{nullptr};
/** Table with all loaded bricks masks */
uint8 *_brickMaskTable[NUM_BRICKS]{nullptr};
/** Table with all loaded bricks sizes */
uint32 _brickSizeTable[NUM_BRICKS]{0};
/** Table with all loaded bricks usage */
uint8 _brickUsageTable[NUM_BRICKS]{0};
/** Current grid pointer */
int32 _currentGridSize = 0;
uint8 *_currentGrid = nullptr;
/** Current block library */
BlockLibraryData _currentBlockLibrary;
/** Brick data buffer */
BrickEntry *_listBrickColon = nullptr;
/** Brick info buffer */
int16 *_nbBrickColon = nullptr;
int32 _brickInfoBufferSize = 0;
/** Celling grid brick block buffer */
int32 _blockBufferSize = 0;
uint8 *_bufCube = nullptr;
const BrickEntry* getBrickEntry(int32 j, int32 i) const;
const IVec3 &updateCollisionCoordinates(int32 x, int32 y, int32 z);
BlockEntry getBlockEntry(int32 xmap, int32 ymap, int32 zmap) const;
bool shouldCheckWaterCol(int32 actorIdx) const;
public:
Grid(TwinEEngine *engine);
~Grid();
void init(int32 w, int32 h);
/**
* search down until either ground is found or lower border of the cube is reached
*/
const uint8 *getBlockBufferGround(const IVec3 &pos, int32 &ground);
/** New grid camera x, y and z coordinates */
IVec3 _startCube; // StartXCube, StartYCube, StartZCube
/** Current grid camera x, y and z coordinates */
IVec3 _worldCube; // WorldXCube WorldYCube
int32 _addBetaCam = 0;
/** Flag to know if the engine is using celling grids */
int16 _zoneGrm = 0;
/** Current celling grid index */
int16 _indexGrm = 0;
/**
* Draw 3D actor over bricks
* @param x actor.x coordinate
* @param y actor.y coordinate
* @param z actor.z coordinate
*/
void drawOverBrick(int32 x, int32 y, int32 z);
/**
* Draw sprite actor over bricks
* @param x actor.x coordinate
* @param y actor.y coordinate
* @param z actor.z coordinate
*/
void drawOverBrick3(int32 x, int32 y, int32 z);
/**
* Get sprite width and height sizes
* @param offset sprite pointer offset
* @param width sprite width size
* @param height sprite height size
* @param spritePtr sprite buffer pointer
*/
void getSpriteSize(int32 offset, int32 *width, int32 *height, const uint8 *spritePtr);
/** recenter screen on followed actor automatically */
void centerScreenOnActor();
void centerOnActor(const ActorStruct* actor);
/**
* Draw brick sprite in the screen
* @param index brick index to draw
* @param posX brick X position to draw
* @param posY brick Y position to draw
*/
bool drawGraph(int32 index, int32 posX, int32 posY);
/**
* Draw sprite in the screen
* @param index sprite index to draw
* @param posX sprite X position to draw
* @param posY sprite Y position to draw
* @param spritePtr sprite buffer pointer to draw
*/
bool drawGraph(int32 index, int32 posX, int32 posY, const uint8 *spritePtr);
bool drawSprite(int32 posX, int32 posY, const SpriteData &ptr, int spriteIndex = 0);
/**
* Draw sprite or bricks in the screen according with the type
* @param posX sprite X position to draw
* @param posY sprite Y position to draw
* @param pGraph sprite buffer pointer to draw
* @param isSprite allows to identify if the sprite to display is brick or a single sprite
*/
bool drawGraph(int32 posX, int32 posY, const uint8 *pGraph, bool isSprite);
/**
* Get block library
* @param blockIdx block library index
* @return pointer to the current block index
*/
const BlockData *getBlockLibrary(int32 blockIdx) const;
const BlockDataEntry* getAdrBlock(int32 blockIdx, int32 tmpBrickIdx) const;
/** Create grid map from current grid to block library buffer */
void copyMapToCube();
/**
* Initialize grid (background scenearios)
* @param index grid index number
*/
bool initGrid(int32 index);
/**
* Initialize celling grid (background scenearios)
* @param index grid index number
*/
bool initCellingGrid(int32 index);
/** Redraw grid background */
void redrawGrid();
ShapeType worldColBrick(int32 x, int32 y, int32 z);
ShapeType worldColBrickFull(int32 x, int32 y, int32 z, int32 y2, int32 actorIdx);
uint8 worldCodeBrick(int32 x, int32 y, int32 z);
inline ShapeType worldColBrick(const IVec3 &pos) {
return worldColBrick(pos.x, pos.y, pos.z);
}
inline ShapeType worldColBrickFull(const IVec3 &pos, int32 y2, int32 actorIdx) {
return worldColBrickFull(pos.x, pos.y, pos.z, y2, actorIdx);
}
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,521 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/movements.h"
#include "common/textconsole.h"
#include "twine/input.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/shadeangletab.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/gamestate.h"
#include "twine/scene/grid.h"
#include "twine/scene/scene.h"
#include "twine/text.h"
#include "twine/twine.h"
namespace TwinE {
Movements::Movements(TwinEEngine *engine) : _engine(engine) {}
IVec3 Movements::getShadow(const IVec3 &pos) { // GetShadow
IVec3 shadowCoord;
const uint8 *ptr = _engine->_grid->getBlockBufferGround(pos, shadowCoord.y);
shadowCoord.x = pos.x;
shadowCoord.z = pos.z;
ShapeType shadowCollisionType;
const int32 blockIdx = *ptr;
if (blockIdx) {
const int32 brickIdx = *(ptr + 1);
const BlockDataEntry *blockPtr = _engine->_grid->getAdrBlock(blockIdx, brickIdx);
shadowCollisionType = (ShapeType)blockPtr->brickShape;
} else {
shadowCollisionType = ShapeType::kNone;
}
_engine->_collision->reajustPos(shadowCoord, shadowCollisionType);
return shadowCoord;
}
void Movements::initRealAngle(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr) {
movePtr->startValue = ClampAngle(startAngle);
movePtr->endValue = ClampAngle(endAngle);
movePtr->timeValue = ClampAngle(stepAngle);
movePtr->memoTicks = _engine->timerRef;
}
void Movements::clearRealAngle(ActorStruct *actorPtr) {
initRealAngle(actorPtr->_beta, actorPtr->_beta, LBAAngles::ANGLE_0, &actorPtr->realAngle);
}
void Movements::initRealValue(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr) {
movePtr->startValue = startAngle;
movePtr->endValue = endAngle;
movePtr->timeValue = stepAngle;
movePtr->memoTicks = _engine->timerRef;
}
int32 Movements::getAngle(int32 x0, int32 z0, int32 x1, int32 z1) {
#if 1
int32 difZ = z1 - z0;
const int32 newZ = difZ * difZ;
int32 difX = x1 - x0;
const int32 newX = difX * difX;
bool flag;
// Exchange X and Z
if (newX < newZ) {
const int32 tmpEx = difX;
difX = difZ;
difZ = tmpEx;
flag = true;
} else {
flag = false;
}
_targetActorDistance = (int32)sqrt((float)(newX + newZ));
if (!_targetActorDistance) {
return 0;
}
const int32 destAngle = (difZ * SCENE_SIZE_HALF) / _targetActorDistance;
int32 startAngle = LBAAngles::ANGLE_0;
// stopAngle = LBAAngles::ANGLE_90;
const int16 *shadeAngleTab3(&sinTab[LBAAngles::ANGLE_135]);
while (shadeAngleTab3[startAngle] > destAngle) {
startAngle++;
}
if (shadeAngleTab3[startAngle] != destAngle) {
if ((shadeAngleTab3[startAngle - 1] + shadeAngleTab3[startAngle]) / 2 <= destAngle) {
startAngle--;
}
}
int32 finalAngle = LBAAngles::ANGLE_45 + startAngle;
if (difX <= 0) {
finalAngle = -finalAngle;
}
if (flag) {
finalAngle = -finalAngle + LBAAngles::ANGLE_90;
}
return ClampAngle(finalAngle);
#else
z1 -= z0;
x1 -= x0;
const int32 x2 = x1 * x1;
const int32 z2 = z1 * z1;
_targetActorDistance = (int32)sqrt((float)(x2 + z2));
if (!_targetActorDistance) {
return 0;
}
if (z2 > x2) {
const int32 tmpEx = z1;
x1 = z1 | 1; // flag = 1
z1 = tmpEx;
} else {
x1 &= -2; // flag = 0
}
const int32 tmp = (z1 * SCENE_SIZE_HALF) / _targetActorDistance;
int32 start = LBAAngles::ANGLE_135;
int32 end = LBAAngles::ANGLE_135 + LBAAngles::ANGLE_90;
int32 diff = 0;
while (start < (end - 1)) {
int32 angle = (start + end) >> 1;
diff = tmp - sinTab[angle];
if (diff > 0) {
end = angle;
} else {
start = angle;
if (diff == 0) {
break;
}
}
}
if (diff) {
if (tmp <= ((sinTab[start] + sinTab[end]) >> 1)) {
start = end;
}
}
int32 angle = start - LBAAngles::ANGLE_90;
if (x1 < 0) {
angle = -angle;
}
if (x1 & 1) {
angle = LBAAngles::ANGLE_90 - angle;
}
return ClampAngle(angle);
#endif
}
void Movements::initRealAngleConst(int32 start, int32 end, int32 duration, RealValue *movePtr) const { // ManualRealAngle
const int16 cstart = ClampAngle(start);
const int16 cend = ClampAngle(end);
movePtr->startValue = cstart;
movePtr->endValue = cend;
const int16 numOfStep = (cstart - cend) * 64;
int32 t = ABS(numOfStep);
t /= 64;
t *= duration;
t /= 256;
movePtr->timeValue = (int16)t;
movePtr->memoTicks = _engine->timerRef;
}
void Movements::ChangedCursorKeys::update(TwinEEngine *engine) {
if (engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
leftChange = leftDown == 0;
leftDown = 1;
} else {
leftChange = leftDown;
leftDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::TurnRight)) {
rightChange = rightDown == 0;
rightDown = 1;
} else {
rightChange = rightDown;
rightDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
backwardChange = backwardDown == 0;
backwardDown = 1;
} else {
backwardChange = backwardDown;
backwardDown = 0;
}
if (engine->_input->isActionActive(TwinEActionType::MoveForward)) {
forwardChange = forwardDown == 0;
forwardDown = 1;
} else {
forwardChange = forwardDown;
forwardDown = 0;
}
}
void Movements::update() {
_previousChangedCursorKeys = _changedCursorKeys;
_previousLoopActionKey = _heroActionKey;
_heroActionKey = _engine->_input->isHeroActionActive();
_changedCursorKeys.update(_engine);
}
void Movements::processBehaviourExecution(int actorIdx) {
switch (_engine->_actor->_heroBehaviour) {
case HeroBehaviourType::kNormal:
_actionNormal = true;
break;
case HeroBehaviourType::kAthletic:
_engine->_animations->initAnim(AnimationTypes::kJump, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case HeroBehaviourType::kAggressive:
if (_engine->_actor->_combatAuto) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
// TODO: previousLoopActionKey must be handled properly
if (!_previousLoopActionKey || actor->_genAnim == AnimationTypes::kStanding) {
const int32 aggresiveMode = _engine->getRandomNumber(3);
switch (aggresiveMode) {
case 0:
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 1:
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
case 2:
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
break;
}
}
} else {
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
_engine->_animations->initAnim(AnimationTypes::kLeftPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
_engine->_animations->initAnim(AnimationTypes::kRightPunch, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
} else if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
_engine->_animations->initAnim(AnimationTypes::kKick, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
}
}
break;
case HeroBehaviourType::kDiscrete:
_engine->_animations->initAnim(AnimationTypes::kHide, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
break;
case HeroBehaviourType::kProtoPack:
case HeroBehaviourType::kMax:
break;
}
}
bool Movements::processAttackExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_gameState->_weapon) {
// Use Magic Ball
if (_engine->_gameState->hasItem(InventoryItems::kiMagicBall)) {
if (_engine->_gameState->_magicBall == -1) {
_engine->_animations->initAnim(AnimationTypes::kThrowBall, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
}
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
return true;
}
} else if (_engine->_gameState->hasItem(InventoryItems::kiUseSabre)) {
if (actor->_genBody != BodyType::btSabre) {
_engine->_actor->initBody(BodyType::btSabre, actorIdx);
}
_engine->_animations->initAnim(AnimationTypes::kSabreAttack, AnimType::kAnimationThen, AnimationTypes::kStanding, actorIdx);
_lastJoyFlag = true;
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
return true;
}
return false;
}
void Movements::processManualMovementExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->isAttackAnimationActive()) {
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
if (actor->isAttackWeaponAnimationActive()) {
return;
}
if (!_changedCursorKeys || _actionNormal) {
// if walking should get stopped
if (!_engine->_input->isActionActive(TwinEActionType::MoveForward) && !_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
if (_lastJoyFlag && (_heroActionKey != _previousLoopActionKey || _changedCursorKeys != _previousChangedCursorKeys)) {
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
}
_lastJoyFlag = false;
if (_engine->_input->isActionActive(TwinEActionType::MoveForward)) {
if (!_engine->_scene->_flagClimbing) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::MoveBackward)) {
_engine->_animations->initAnim(AnimationTypes::kBackward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
_lastJoyFlag = true;
}
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnLeft, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
}
_lastJoyFlag = true;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
if (actor->_genAnim == AnimationTypes::kStanding) {
_engine->_animations->initAnim(AnimationTypes::kTurnRight, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
} else {
if (!actor->_workFlags.bIsRotationByAnim) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
}
_lastJoyFlag = true;
}
}
}
void Movements::processManualRotationExecution(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (!_engine->_actor->_combatAuto && actor->isAttackAnimationActive()) {
// it is allowed to rotate in auto aggressive mode - but not in manual mode.
return;
}
if (actor->isJumpAnimationActive()) {
return;
}
int16 tempAngle;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
} else {
tempAngle = LBAAngles::ANGLE_0;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_srot, &actor->realAngle);
}
void Movements::processManualAction(int actorIdx) {
if (IS_HERO(actorIdx)) {
_actionNormal = false;
if (_engine->_input->isHeroActionActive()) {
processBehaviourExecution(actorIdx);
} else if (_engine->_input->toggleActionIfActive(TwinEActionType::SpecialAction)) {
_actionNormal = true;
}
// MyFire & F_ALT
if (_engine->_input->isActionActive(TwinEActionType::ThrowMagicBall) && !_engine->_gameState->inventoryDisabled()) {
processAttackExecution(actorIdx);
}
}
processManualMovementExecution(actorIdx);
processManualRotationExecution(actorIdx);
}
void Movements::processFollowAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
int32 newAngle = getAngle(actor->posObj(), followedActor->posObj());
if (actor->_flags.bSprite3D) {
actor->_beta = newAngle;
} else {
initRealAngleConst(actor->_beta, newAngle, actor->_srot, &actor->realAngle);
}
}
void Movements::processRandomAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_workFlags.bIsRotationByAnim) {
return;
}
if (actor->brickCausesDamage()) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90 + LBAAngles::ANGLE_180);
initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
actor->_delayInMillis = _engine->timerRef + _engine->getRandomNumber(_engine->toSeconds(6)) + _engine->toSeconds(6);
_engine->_animations->initAnim(AnimationTypes::kStanding, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
}
if (!actor->realAngle.timeValue) {
_engine->_animations->initAnim(AnimationTypes::kForward, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx);
if (_engine->timerRef > actor->_delayInMillis) {
const int32 angle = ClampAngle(actor->_beta + (_engine->getRandomNumber() & (LBAAngles::ANGLE_180 - 1)) - LBAAngles::ANGLE_90);
initRealAngleConst(actor->_beta, angle, actor->_srot, &actor->realAngle);
actor->_delayInMillis = _engine->timerRef + _engine->getRandomNumber(_engine->toSeconds(6)) + _engine->toSeconds(6);
}
}
}
void Movements::processTrackAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_offsetTrack == -1) {
actor->_offsetTrack = 0;
}
}
void Movements::processSameXZAction(int actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const ActorStruct *followedActor = _engine->_scene->getActor(actor->_followedActor);
actor->_posObj.x = followedActor->_posObj.x;
actor->_posObj.z = followedActor->_posObj.z;
}
void Movements::manualRealAngle(ActorStruct *actor) {
int16 tempAngle = LBAAngles::ANGLE_0;
if (_engine->_input->isActionActive(TwinEActionType::TurnLeft)) {
tempAngle = LBAAngles::ANGLE_90;
} else if (_engine->_input->isActionActive(TwinEActionType::TurnRight)) {
tempAngle = -LBAAngles::ANGLE_90;
}
initRealAngleConst(actor->_beta, actor->_beta + tempAngle, actor->_srot, &actor->realAngle);
}
void Movements::doDir(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_body == -1) {
return;
}
if (actor->_workFlags.bIsFalling) {
if (actor->_move == ControlMode::kManual) {
manualRealAngle(actor);
// TODO: _lastJoyFlag = _joyFlag;
}
return;
}
if (!actor->_flags.bSprite3D && actor->_move != ControlMode::kManual) {
actor->_beta = actor->realAngle.getRealAngle(_engine->timerRef);
}
switch (actor->_move) {
case ControlMode::kManual:
processManualAction(actorIdx);
break;
case ControlMode::kFollow:
processFollowAction(actorIdx);
break;
case ControlMode::kRandom:
processRandomAction(actorIdx);
break;
case ControlMode::kTrack:
processTrackAction(actorIdx);
break;
case ControlMode::kSameXZ:
// TODO: see lSET_DIRMODE and lSET_DIRMODE_OBJ opcodes
processSameXZAction(actorIdx);
break;
/**
* The Actor's Track Script is stopped. Track Script execution may be started with Life Script of
* the Actor or other Actors (with SET_TRACK(_OBJ) command). This mode does not mean the Actor
* will literally not move, but rather that it's Track Script (also called Move Script) is
* initially stopped. The Actor may move if it is assigned a moving animation.
*/
case ControlMode::kNoMove:
case ControlMode::kFollow2: // unused
case ControlMode::kTrackAttack: // unused
break;
default:
warning("Unknown control mode %d", (int)actor->_move);
break;
}
}
} // namespace TwinE

View File

@@ -0,0 +1,201 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_MOVEMENTS_H
#define TWINE_SCENE_MOVEMENTS_H
#include "common/scummsys.h"
#include "twine/shared.h"
namespace TwinE {
class TwinEEngine;
class ActorStruct;
struct RealValue;
class Movements {
private:
TwinEEngine *_engine;
struct ChangedCursorKeys {
uint8 forwardChange = 0;
uint8 backwardChange = 0;
uint8 leftChange = 0;
uint8 rightChange = 0;
uint8 forwardDown = 0;
uint8 backwardDown = 0;
uint8 leftDown = 0;
uint8 rightDown = 0;
void update(TwinEEngine *engine);
inline bool operator==(const ChangedCursorKeys &rhs) const {
return forwardChange == rhs.forwardChange && backwardChange == rhs.backwardChange && leftChange == rhs.leftChange && rightChange == rhs.rightChange;
}
inline operator bool() const {
return forwardChange && backwardChange && leftChange && rightChange;
}
inline bool operator!=(const ChangedCursorKeys &rhs) const {
return forwardChange != rhs.forwardChange || backwardChange != rhs.backwardChange || leftChange != rhs.leftChange || rightChange != rhs.rightChange;
}
};
// enter, space, ...
int16 _heroActionKey = 0;
int32 _previousLoopActionKey = 0;
// cursor keys
ChangedCursorKeys _changedCursorKeys;
ChangedCursorKeys _previousChangedCursorKeys;
/**
* The Actor is controlled by the player. This works well only for the Hero Actor in general.
* To use it for other Actors they would have to have necessary animations that would be also
* correctly indexed. The primary purpose for this mode is to re-enable player's control over
* the Hero after it has been disabled for some reasons.
*/
void processManualAction(int actorIdx);
/**
* The Actor tries to move towards the target Actor. This only means that it will always face
* in its direction (as fast as the Rotation delay property allows). To make it really follow
* anything it must be assigned a moving Animation first, and the Actor will not stop by itself
* after reaching the target. To make a real following, the Actor's animation must be changed
* for example to standing animation when the Actor is near the target, and changed back to a
* moving animation when it's far from it. The Follow mode handles only the facing angle.
*/
void processFollowAction(int actorIdx);
/**
* Makes the Actor walk and turn by random angles and at random moments. In original game it is
* only used for Nitro-Mecha-Penguins, but it can be used for any 3-D Actor that has standing
* and walking animation (with virtual indexes 0 and 1 respectively). This mode requires the
* Randomize interval (Info1) property to be less or equal to 117, otherwise the Actor will just
* walk without turning. Exact meaning of the property is not known.
*/
void processRandomAction(int actorIdx);
/**
* The Actor's Track Script is run from the first command, and when it reaches END or STOP it
* starts over again.
*/
void processTrackAction(int actorIdx);
/**
* This mode is used to make an Actor follow specified Actor's X and Z (horizontal) coordinates.
* This is mainly used for Sprite Actors to be always above other Sprite Actors (like platforms).
* Unlike the Follow mode, this mode sets the Actor's position. If the Actor is a Sprite Actor,
* its speed is not taken into consideration in this mode.
*/
void processSameXZAction(int actorIdx);
void processBehaviourExecution(int actorIdx);
bool processAttackExecution(int actorIdx);
void processManualMovementExecution(int actorIdx);
void processManualRotationExecution(int actorIdx);
/**
* @return A value of @c true means that the actor should e.g. start reading a sign or checking
* a locker for loot or secrets or talking to an npc - this can get triggered by the SpecialAction binding
* in any behaviour mode
*/
bool _actionNormal = false;
void manualRealAngle(ActorStruct *actor);
public:
Movements(TwinEEngine *engine);
void setActionNormal(bool actionNormal);
void update();
/**
* Hero executes the current action of the trigger zone
*/
bool actionNormal() const;
bool _lastJoyFlag = false;
int32 _targetActorDistance = 0;
/**
* Get shadow position
* @param pos Shadow coordinates
*/
IVec3 getShadow(const IVec3 &pos);
/**
* Set actor safe angle
* @param startAngle start angle
* @param endAngle end angle
* @param stepAngle number of steps
* @param movePtr time pointer to update
*/
void initRealAngle(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr);
/**
* Clear actors safe angle
* @param actorPtr actor pointer
*/
void clearRealAngle(ActorStruct *actorPtr);
/**
* Set actor safe angle
* @param startAngle start angle
* @param endAngle end angle
* @param stepAngle number of steps
* @param movePtr time pointer to update
*/
void initRealValue(int16 startAngle, int16 endAngle, int16 stepAngle, RealValue *movePtr);
/**
* Get actor angle
* @param x1 Actor 1 X
* @param z1 Actor 1 Z
* @param x2 Actor 2 X
* @param z2 Actor 2 Z
*/
int32 getAngle(int32 x1, int32 z1, int32 x2, int32 z2);
inline int32 getAngle(const IVec3& v1, const IVec3 &v2) {
return getAngle(v1.x, v1.z, v2.x, v2.z);
}
/**
* Move actor around the scene
* @param start Current actor angle
* @param end Angle to rotate
* @param duration Rotate speed
* @param movePtr Pointer to process movements
*/
void initRealAngleConst(int32 start, int32 end, int32 duration, RealValue *movePtr) const;
void doDir(int32 actorIdx);
};
inline void Movements::setActionNormal(bool actionNormal) {
_actionNormal = actionNormal;
}
inline bool Movements::actionNormal() const {
return _actionNormal;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,170 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/rain.h"
#include "twine/renderer/renderer.h"
#include "twine/scene/grid.h"
#include "twine/twine.h"
namespace TwinE {
#define RAIN_VX 200
#define RAIN_VY 2500
#define RAIN_WEIGHT 30
#define RAIN_STOP 0
#define RAIN_DELTA_X 128
#define RAIN_DELTA_Y 256
#define RAIN_DELTA_Z 128
void Rain::InitOneRain(T_RAIN *pt) {
IVec3 cameraPos = _engine->_renderer->getCameraPosition();
int32 rndy = _engine->getRandomNumber(cameraPos.y + 10000);
pt->YRain = cameraPos.y + rndy;
rndy = rndy / 2 + 15000;
pt->XRain = cameraPos.x - rndy + _engine->getRandomNumber(30000);
pt->ZRain = cameraPos.z - rndy + _engine->getRandomNumber(30000);
pt->Timer = 0;
}
void Rain::InitRain() {
for (int32 i = 0; i < MAX_RAIN; i++) {
InitOneRain(&TabRain[i]);
}
LastTimer = 0;
}
void Rain::GereRain() {
int32 temp = _engine->timerRef;
DeltaRain = LastTimer ? (temp - LastTimer) * 10 : 0;
LastTimer = temp;
for (int32 i = 0; i < MAX_RAIN; i++) {
if (!TabRain[i].Timer) {
TabRain[i].XRain += DeltaRain / 2;
TabRain[i].ZRain += DeltaRain / 2;
TabRain[i].YRain -= DeltaRain;
}
}
}
void Rain::ClearImpactRain() {
for (int32 i = 0; i < MAX_RAIN; i++) {
if (TabRain[i].Timer) {
InitOneRain(&TabRain[i]);
}
}
}
void Rain::AffRain() {
int32 lFactorX = _engine->_renderer->getLFactorX();
int32 lFactorY = _engine->_renderer->getLFactorY();
IVec3 cameraRot = _engine->_renderer->getCameraRotation();
int32 cameraZr = cameraRot.z;
// ClipZFar approximation
int32 clipZFar = 14000; // Default value from CREDITS.CPP
int32 startZFog = 5000; // Default value from CREDITS.CPP
for (int32 i = 0; i < MAX_RAIN; i++) {
if (TabRain[i].Timer) {
int32 dt = LastTimer - TabRain[i].Timer;
int32 c = TabRain[i].XRain >> 16;
int32 x = (int16)(TabRain[i].XRain & 0xFFFF);
int32 y = TabRain[i].YRain;
int32 z = TabRain[i].ZRain;
int32 xp, yp;
yp = (RAIN_VY - RAIN_WEIGHT * dt) * dt / 256;
if (yp < 0) {
yp = 0;
xp = RAIN_VX * RAIN_VY / RAIN_WEIGHT / 256;
} else {
xp = RAIN_VX * dt / 256;
yp = (yp * lFactorY) / z;
}
xp = (xp * lFactorX) / z;
int32 x0 = x - xp;
int32 x1 = x + xp;
// int32 y0 = y - yp;
// int32 y1 = y;
// z = ruleThree32(0, 65535, clipZFar, z);
// Draw splash
_engine->_workVideoBuffer.drawLine(x, y, x0, y - yp, c);
_engine->_workVideoBuffer.drawLine(x, y, x1, y - yp, c);
if (dt && !yp) {
InitOneRain(&TabRain[i]);
}
} else {
if (TabRain[i].YRain <= RAIN_STOP) {
InitOneRain(&TabRain[i]);
continue;
}
IVec3 p1 = _engine->_renderer->longWorldRot(TabRain[i].XRain - RAIN_DELTA_X, TabRain[i].YRain + RAIN_DELTA_Y, TabRain[i].ZRain - RAIN_DELTA_Z);
IVec3 proj1 = _engine->_renderer->projectPoint(p1);
int32 xp = proj1.x;
int32 yp = proj1.y;
int32 z0 = ruleThree32(0, 65535, clipZFar, cameraZr - p1.z);
IVec3 p2 = _engine->_renderer->longWorldRot(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
IVec3 proj2 = _engine->_renderer->projectPoint(p2);
int32 Z0 = cameraZr - p2.z;
// int32 z1 = ruleThree32(0, 65535, clipZFar, Z0);
int32 c = boundRuleThree(16 * 3 + 10, 16 * 3 + 3, clipZFar - startZFog, Z0);
// Draw rain drop
_engine->_workVideoBuffer.drawLine(xp, yp, proj2.x, proj2.y, c);
// Check collision with ground
int32 groundHeight = 0;
IVec3 pos(TabRain[i].XRain, TabRain[i].YRain, TabRain[i].ZRain);
_engine->_grid->getBlockBufferGround(pos, groundHeight);
if (TabRain[i].YRain <= groundHeight) {
// Splash
TabRain[i].XRain = ((xp & 0xFFFF) | (c << 16));
TabRain[i].YRain = yp;
TabRain[i].ZRain = z0;
TabRain[i].Timer = LastTimer;
}
}
}
}
Rain::Rain(TwinEEngine *engine) : _engine(engine) {
}
} // namespace TwinE

View File

@@ -0,0 +1,59 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_RAIN_H
#define TWINE_SCENE_RAIN_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
namespace TwinE {
#define MAX_RAIN 200
class Rain {
private:
TwinEEngine *_engine;
int32 LastTimer = 0;
int32 DeltaRain = 0;
public:
struct T_RAIN {
int32 XRain = 0;
int32 YRain = 0;
int32 ZRain = 0;
int32 Timer = 0;
};
T_RAIN TabRain[MAX_RAIN];
Rain(TwinEEngine *engine);
void InitOneRain(T_RAIN *pt);
void InitRain();
void GereRain();
void ClearImpactRain();
void AffRain();
};
} // namespace TwinE
#endif

View File

@@ -0,0 +1,885 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/scene.h"
#include "twine/scene/rain.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "engines/enhancements.h"
#include "twine/audio/music.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/holomap.h"
#include "twine/renderer/redraw.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.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/text.h"
#include "twine/twine.h"
namespace TwinE {
Scene::~Scene() {
free(_currentScene);
}
void Scene::setActorStaticFlags(ActorStruct *act, uint32 staticFlags) {
if (staticFlags & 0x1) {
act->_flags.bComputeCollisionWithObj = 1;
}
if (staticFlags & 0x2) {
act->_flags.bComputeCollisionWithBricks = 1;
}
if (staticFlags & 0x4) {
act->_flags.bCheckZone = 1;
}
if (staticFlags & 0x8) {
act->_flags.bSpriteClip = 1;
}
if (staticFlags & 0x10) {
act->_flags.bCanBePushed = 1;
}
if (staticFlags & 0x20) {
act->_flags.bComputeLowCollision = 1;
}
if (staticFlags & 0x40) {
act->_flags.bCanDrown = 1;
}
if (staticFlags & 0x80) {
act->_flags.bComputeCollisionWithFloor = 1;
}
if (staticFlags & 0x100) {
act->_flags.bUnk0100 = 1;
}
if (staticFlags & 0x200) {
act->_flags.bIsInvisible = 1;
}
if (staticFlags & 0x400) {
act->_flags.bSprite3D = 1;
}
if (staticFlags & 0x800) {
act->_flags.bObjFallable = 1;
}
if (staticFlags & 0x1000) {
act->_flags.bNoShadow = 1;
}
if (staticFlags & 0x2000) {
act->_flags.bIsBackgrounded = 1;
}
if (staticFlags & 0x4000) {
act->_flags.bIsCarrierActor = 1;
}
if (staticFlags & 0x8000) {
act->_flags.bUseMiniZv = 1;
}
if (staticFlags & 0x10000) {
act->_flags.bHasInvalidPosition = 1;
}
if (staticFlags & 0x20000) {
act->_flags.bNoElectricShock = 1;
}
if (staticFlags & 0x40000) {
act->_flags.bHasSpriteAnim3D = 1;
}
if (staticFlags & 0x80000) {
act->_flags.bNoPreClipping = 1;
}
if (staticFlags & 0x100000) {
act->_flags.bHasZBuffer = 1;
}
if (staticFlags & 0x200000) {
act->_flags.bHasZBufferInWater = 1;
}
}
void Scene::setBonusParameterFlags(ActorStruct *act, uint16 bonusFlags) {
if (bonusFlags & 0x1) {
act->_bonusParameter.givenNothing = 1;
}
if (bonusFlags & 0x2) {
act->_bonusParameter.unk2 = 1;
}
if (bonusFlags & 0x4) {
act->_bonusParameter.unk3 = 1;
}
if (bonusFlags & 0x8) {
act->_bonusParameter.unk4 = 1;
}
if (bonusFlags & 0x10) {
act->_bonusParameter.kashes = 1;
}
if (bonusFlags & 0x20) {
act->_bonusParameter.lifepoints = 1;
}
if (bonusFlags & 0x40) {
act->_bonusParameter.magicpoints = 1;
}
if (bonusFlags & 0x80) {
act->_bonusParameter.key = 1;
}
if (bonusFlags & 0x100) {
act->_bonusParameter.cloverleaf = 1;
}
}
bool Scene::loadSceneCubeXY(int numcube, int32 *cubex, int32 *cubey) {
uint8 *scene = nullptr;
// numcube+1 because at 0 is SizeCube.MAX (size of the largest .SCC)
const int32 sceneSize = HQR::getAllocEntry(&scene, Resources::HQR_SCENE_FILE, numcube + 1);
if (sceneSize <= 0) {
return false;
}
Common::MemoryReadStream stream(scene, sceneSize, DisposeAfterUse::YES);
*cubex = *cubey = 0;
// World info: INFO_WORLD
const uint8 island = stream.readByte();
// Used only for 3DExt
const int32 x = stream.readByte();
const int32 y = stream.readByte();
/*uint8 shadowlvl =*/stream.readByte();
/*uint8 modelaby =*/stream.readByte();
const uint8 cubemode = stream.readByte();
if (cubemode == CUBE_EXTERIEUR && island == _island && ABS(x - _currentCubeX) <= 1 && ABS(y - _currentCubeY) <= 1) {
*cubex = x;
*cubey = y;
return true;
}
return false;
}
void Scene::loadModel(ActorStruct &actor, int32 modelIndex, bool lba1) {
actor._body = modelIndex;
if (!actor._flags.bSprite3D) {
debug(1, "Init actor with model %i", modelIndex);
_engine->_resources->loadEntityData(actor._entityData, modelIndex);
actor._entityDataPtr = &actor._entityData;
} else {
actor._entityDataPtr = nullptr;
}
}
bool Scene::loadSceneLBA2() {
Common::MemoryReadStream stream(_currentScene, _currentSceneSize);
_island = stream.readByte();
_sceneTextBank = (TextBankId)_island;
_currentCubeX = stream.readByte();
_currentCubeY = stream.readByte();
_shadowLevel = stream.readByte();
_modeLabyrinthe = stream.readByte();
_isOutsideScene = stream.readByte();
/*uint8 n =*/ stream.readByte();
_alphaLight = ClampAngle(stream.readSint16LE());
_betaLight = ClampAngle(stream.readSint16LE());
debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight);
for (int i = 0; i < 4; ++i) {
_sampleAmbiance[i] = stream.readUint16LE();
_sampleRepeat[i] = stream.readUint16LE();
_sampleRound[i] = stream.readUint16LE();
_sampleFrequency[i] = stream.readUint16LE();
_sampleVolume[i] = stream.readUint16LE();
}
_sampleMinDelay = stream.readUint16LE();
_sampleMinDelayRnd = stream.readUint16LE();
_cubeJingle = stream.readByte();
// load hero properties
_sceneHeroPos.x = stream.readSint16LE();
_sceneHeroPos.y = stream.readSint16LE();
_sceneHeroPos.z = stream.readSint16LE();
_sceneHero->_moveScriptSize = (int16)stream.readUint16LE();
_sceneHero->_ptrTrack = _currentScene + stream.pos();
stream.skip(_sceneHero->_moveScriptSize);
_sceneHero->_lifeScriptSize = (int16)stream.readUint16LE();
_sceneHero->_lifeScript = _currentScene + stream.pos();
stream.skip(_sceneHero->_lifeScriptSize);
_nbObjets = (int16)stream.readUint16LE();
int cnt = 1;
for (int32 a = 1; a < _nbObjets; a++, cnt++) {
_engine->_actor->initObject(a);
ActorStruct *act = &_sceneActors[a];
setActorStaticFlags(act, stream.readUint32LE());
loadModel(*act, (int16)stream.readUint16LE(), false);
act->_genBody = (BodyType)stream.readByte();
act->_genAnim = (AnimationTypes)stream.readSint16LE();
act->_sprite = (int16)stream.readUint16LE();
act->_posObj.x = (int16)stream.readUint16LE();
act->_posObj.y = (int16)stream.readUint16LE();
act->_posObj.z = (int16)stream.readUint16LE();
act->_oldPos = act->posObj();
act->_hitForce = stream.readByte();
setBonusParameterFlags(act, stream.readUint16LE());
act->_beta = (int16)stream.readUint16LE();
act->_srot = (int16)stream.readUint16LE();
act->_move = (ControlMode)stream.readByte(); // move
act->_cropLeft = stream.readSint16LE();
act->_delayInMillis = act->_cropLeft; // TODO: this might not be needed
act->_cropTop = stream.readSint16LE();
act->_cropRight = stream.readSint16LE();
act->_cropBottom = stream.readSint16LE();
act->_followedActor = act->_cropBottom; // TODO: is this needed? and valid?
act->_bonusAmount = stream.readSint16LE();
act->_talkColor = stream.readByte();
if (act->_flags.bHasSpriteAnim3D) {
/*act->spriteAnim3DNumber = */stream.readSint32LE();
/*act->spriteSizeHit = */stream.readSint16LE();
/*act->cropBottom = act->spriteSizeHit;*/
}
act->_armor = stream.readByte();
act->setLife(stream.readByte());
act->_moveScriptSize = (int16)stream.readUint16LE();
act->_ptrTrack = _currentScene + stream.pos();
stream.skip(act->_moveScriptSize);
act->_lifeScriptSize = (int16)stream.readUint16LE();
act->_lifeScript = _currentScene + stream.pos();
stream.skip(act->_lifeScriptSize);
if (_engine->_debugState->_onlyLoadActor != -1 && _engine->_debugState->_onlyLoadActor != cnt) {
_nbObjets--;
a--;
}
}
/* uint32 checksum = */stream.readUint32LE();
_sceneNumZones = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumZones; i++) {
ZoneStruct *zone = &_sceneZones[i];
zone->mins.x = stream.readSint32LE();
zone->mins.y = stream.readSint32LE();
zone->mins.z = stream.readSint32LE();
zone->maxs.x = stream.readSint32LE();
zone->maxs.y = stream.readSint32LE();
zone->maxs.z = stream.readSint32LE();
zone->infoData.generic.info0 = stream.readSint32LE();
zone->infoData.generic.info1 = stream.readSint32LE();
zone->infoData.generic.info2 = stream.readSint32LE();
zone->infoData.generic.info3 = stream.readSint32LE();
zone->infoData.generic.info4 = stream.readSint32LE();
zone->infoData.generic.info5 = stream.readSint32LE();
zone->infoData.generic.info6 = stream.readSint32LE();
zone->infoData.generic.info7 = stream.readSint32LE();
zone->type = (ZoneType)stream.readUint16LE();
zone->num = stream.readSint16LE();
}
_sceneNumTracks = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumTracks; i++) {
IVec3 *point = &_sceneTracks[i];
point->x = stream.readSint32LE();
point->y = stream.readSint32LE();
point->z = stream.readSint32LE();
}
uint16 sceneNumPatches = stream.readUint32LE();
for (uint16 i = 0; i < sceneNumPatches; i++) {
/*size = */stream.readUint16LE();
/*offset = */stream.readUint16LE();
}
return true;
}
// LoadScene
bool Scene::loadSceneLBA1() {
Common::MemoryReadStream stream(_currentScene, _currentSceneSize);
// load scene ambience properties
_sceneTextBank = (TextBankId)stream.readByte();
_currentGameOverScene = stream.readByte();
stream.skip(4);
// FIXME: Workaround to fix lighting issue - not using proper dark light
// Using 1215 and 1087 as light vectors - scene 8
_alphaLight = ClampAngle((int16)stream.readUint16LE());
_betaLight = ClampAngle((int16)stream.readUint16LE());
debug(2, "Using %i and %i as light vectors", _alphaLight, _betaLight);
for (int i = 0; i < 4; ++i) {
_sampleAmbiance[i] = stream.readUint16LE();
_sampleRepeat[i] = stream.readUint16LE();
_sampleRound[i] = stream.readUint16LE();
}
_sampleMinDelay = stream.readUint16LE();
_sampleMinDelayRnd = stream.readUint16LE();
_cubeJingle = stream.readByte();
// load hero properties
_sceneHeroPos.x = (int16)stream.readUint16LE();
_sceneHeroPos.y = (int16)stream.readUint16LE();
_sceneHeroPos.z = (int16)stream.readUint16LE();
_sceneHero->_moveScriptSize = (int16)stream.readUint16LE();
_sceneHero->_ptrTrack = _currentScene + stream.pos();
stream.skip(_sceneHero->_moveScriptSize);
_sceneHero->_lifeScriptSize = (int16)stream.readUint16LE();
_sceneHero->_lifeScript = _currentScene + stream.pos();
stream.skip(_sceneHero->_lifeScriptSize);
_nbObjets = (int16)stream.readUint16LE();
int cnt = 1;
for (int32 a = 1; a < _nbObjets; a++, cnt++) {
_engine->_actor->initObject(a);
ActorStruct *act = &_sceneActors[a];
setActorStaticFlags(act, stream.readUint16LE());
loadModel(*act, stream.readUint16LE(), true);
act->_genBody = (BodyType)stream.readByte();
act->_genAnim = (AnimationTypes)stream.readByte();
act->_sprite = (int16)stream.readUint16LE();
act->_posObj.x = (int16)stream.readUint16LE();
act->_posObj.y = (int16)stream.readUint16LE();
act->_posObj.z = (int16)stream.readUint16LE();
act->_oldPos = act->posObj();
act->_hitForce = stream.readByte();
setBonusParameterFlags(act, stream.readUint16LE());
act->_bonusParameter.givenNothing = 0;
act->_beta = (int16)stream.readUint16LE();
act->_srot = (int16)stream.readUint16LE();
act->_move = (ControlMode)stream.readUint16LE();
act->_cropLeft = stream.readSint16LE();
act->_delayInMillis = act->_cropLeft; // TODO: this might not be needed
act->_cropTop = stream.readSint16LE();
act->_cropRight = stream.readSint16LE();
act->_cropBottom = stream.readSint16LE();
act->_followedActor = act->_cropBottom; // TODO: is this needed? and valid?
act->_bonusAmount = stream.readByte();
act->_talkColor = stream.readByte();
act->_armor = stream.readByte();
act->setLife(stream.readByte());
act->_moveScriptSize = (int16)stream.readUint16LE();
act->_ptrTrack = _currentScene + stream.pos();
stream.skip(act->_moveScriptSize);
act->_lifeScriptSize = (int16)stream.readUint16LE();
act->_lifeScript = _currentScene + stream.pos();
stream.skip(act->_lifeScriptSize);
if (_engine->_debugState->_onlyLoadActor != -1 && _engine->_debugState->_onlyLoadActor != cnt) {
_nbObjets--;
a--;
}
}
_sceneNumZones = (int16)stream.readUint16LE();
for (int32 i = 0; i < _sceneNumZones; i++) {
ZoneStruct *zone = &_sceneZones[i];
zone->mins.x = stream.readSint16LE();
zone->mins.y = stream.readSint16LE();
zone->mins.z = stream.readSint16LE();
zone->maxs.x = stream.readSint16LE();
zone->maxs.y = stream.readSint16LE();
zone->maxs.z = stream.readSint16LE();
zone->type = (ZoneType)stream.readUint16LE();
zone->num = stream.readSint16LE();
zone->infoData.generic.info0 = stream.readSint16LE();
zone->infoData.generic.info1 = stream.readSint16LE();
zone->infoData.generic.info2 = stream.readSint16LE();
zone->infoData.generic.info3 = stream.readSint16LE();
}
_sceneNumTracks = stream.readUint16LE();
for (int32 i = 0; i < _sceneNumTracks; i++) {
IVec3 *point = &_sceneTracks[i];
point->x = stream.readSint16LE();
point->y = stream.readSint16LE();
point->z = stream.readSint16LE();
}
if (_engine->isLBA1()) {
if (_engine->enhancementEnabled(kEnhMinorBugFixes)) {
if (_numCube == LBA1SceneId::Hamalayi_Mountains_landing_place) {
// move the mine a little bit, as it's too close to the change cube zone
_sceneActors[21]._posObj.x = _sceneActors[21]._oldPos.x = 6656 + 256;
_sceneActors[21]._posObj.z = _sceneActors[21]._oldPos.z = 768;
}
#if 0
else if (_numCube == LBA1SceneId::Tippet_Island_Secret_passage_scene_1) {
_sceneZones[6].maxs.z = 3616;
}
#endif
}
if (_engine->enhancementEnabled(kEnhGameBreakingBugFixes)) {
if (_numCube == LBA1SceneId::Principal_Island_outside_the_fortress) {
// https://bugs.scummvm.org/ticket/13818
_sceneActors[29]._posObj.z = _sceneActors[29]._oldPos.z = 1795;
} else if (_numCube == LBA1SceneId::Principal_Island_inside_the_fortress) {
// https://bugs.scummvm.org/ticket/13819
// Set this zone to something invalid to fix a getting-stuck-bug
// the original value was ZoneType::kGrid (3)
_sceneZones[11].type = ZoneType::kFunFrockFix;
}
}
}
return true;
}
bool Scene::loadScene(int32 index) {
// load scene from file
if (_engine->isLBA2()) {
index++;
}
_currentSceneSize = HQR::getAllocEntry(&_currentScene, Resources::HQR_SCENE_FILE, index);
if (_currentSceneSize == 0) {
return false;
}
if (_engine->isLBA1()) {
return loadSceneLBA1();
} else if (_engine->isLBA2()) {
_engine->_rain->InitRain();
return loadSceneLBA2();
}
return false;
}
void Scene::clearScene() {
_engine->_extra->clearExtra();
// ClearFlagsCube
for (int32 i = 0; i < ARRAYSIZE(_listFlagCube); i++) {
_listFlagCube[i] = 0;
}
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
_engine->_redraw->overlayList[i].num = -1;
}
_engine->_screens->_flagPalettePcx = false;
}
void Scene::reloadCurrentScene() {
_newCube = _numCube;
}
void Scene::dumpSceneScript(const char *type, int actorIdx, const uint8* script, int size) const {
Common::String fname = Common::String::format("./dumps/%i-%i.%s", _numCube, actorIdx, type);
Common::DumpFile out;
if (!out.open(fname.c_str(), true)) {
warning("Scene::dumpSceneScript(): Can not open dump file %s", fname.c_str());
} else {
out.write(script, size);
out.flush();
out.close();
}
}
void Scene::dumpSceneScripts() const {
for (int32 a = 0; a < _nbObjets; ++a) {
const ActorStruct &actor = _sceneActors[a];
dumpSceneScript("life", a, actor._lifeScript, actor._lifeScriptSize);
dumpSceneScript("move", a, actor._ptrTrack, actor._moveScriptSize);
}
}
void Scene::changeCube() {
if (_engine->isLBA1()) {
if (_engine->enhancementEnabled(kEnhMinorBugFixes)) {
if (_numCube == LBA1SceneId::Citadel_Island_Harbor && _newCube == LBA1SceneId::Principal_Island_Harbor) {
if (_sceneNumZones >= 15 && _sceneNumTracks >= 8) {
const ZoneStruct *zone = &_sceneZones[15];
const IVec3 &track = _sceneTracks[8];
IVec3 &pos = _zoneHeroPos;
pos.x = zone->infoData.ChangeScene.x - zone->mins.x + track.x;
pos.y = zone->infoData.ChangeScene.y - zone->mins.y + track.y;
pos.z = zone->infoData.ChangeScene.z - zone->mins.z + track.z;
_engine->_scene->_flagChgCube = ScenePositionType::kZone;
debug(2, "Using zone position %i:%i:%i", pos.x, pos.y, pos.z);
}
}
}
// change twinsen house destroyed hard-coded
if (_newCube == LBA1SceneId::Citadel_Island_near_twinsens_house && _engine->_gameState->hasOpenedFunfrocksSafe()) {
_newCube = LBA1SceneId::Citadel_Island_Twinsens_house_destroyed;
}
}
// local backup previous scene
_oldcube = _numCube;
_numCube = _newCube;
snprintf(_engine->_gameState->_sceneName, sizeof(_engine->_gameState->_sceneName), "%i %s", _numCube, _engine->_holomap->getLocationName(_numCube));
debug(2, "Entering scene %s (came from %i)", _engine->_gameState->_sceneName, _oldcube);
if (_engine->isLBA1()) {
if (_newCube == LBA1SceneId::Polar_Island_end_scene) {
_engine->unlockAchievement("LBA_ACH_001");
// if you finish the game in less than 4 hours
if (_engine->getTotalPlayTime() <= 1000 * 60 * 60 * 4) {
_engine->unlockAchievement("LBA_ACH_005");
}
} else if (_newCube == LBA1SceneId::Brundle_Island_Secret_room) {
_engine->unlockAchievement("LBA_ACH_006");
}
}
_engine->_sound->stopSamples();
clearScene();
_engine->_actor->loadHeroEntities();
_sceneHero->_move = ControlMode::kManual;
_sceneHero->_zoneSce = -1;
_sceneHero->_offsetLife = 0;
_sceneHero->_offsetTrack = -1;
_sceneHero->_labelTrack = -1;
loadScene(_newCube);
if (ConfMan.getBool("dump_scripts")) {
dumpSceneScripts();
}
if (_numHolomapTraj != -1) {
_engine->testRestoreModeSVGA(false);
_engine->_screens->setBlackPal();
_engine->_holomap->holoTraj(_numHolomapTraj);
_numHolomapTraj = -1;
_engine->_screens->_flagFade = true;
} else {
// TODO lbawin can do a fade here (if activated)
// _engine->_screens->_flagFade = true;
}
if (_newCube == LBA1SceneId::Citadel_Island_end_sequence_1 || _newCube == LBA1SceneId::Citadel_Island_end_sequence_2) {
_sceneTextBank = TextBankId::Tippet_Island;
}
_engine->_text->initSceneTextBank();
if (_cubeJingle != 255) {
// _engine->_music->fadeMusicMidi(1);
}
_engine->_grid->initGrid(_newCube);
if (_flagChgCube == ScenePositionType::kZone) {
_sceneStart = _zoneHeroPos;
} else if (_flagChgCube == ScenePositionType::kScene || _flagChgCube == ScenePositionType::kNoPosition) {
_sceneStart = _sceneHeroPos;
}
_sceneHero->_posObj = _sceneStart;
_startYFalling = _sceneStart.y;
_engine->_renderer->setLightVector(_alphaLight, _betaLight, LBAAngles::ANGLE_0);
if (_oldcube != SCENE_CEILING_GRID_FADE_1 && _oldcube != _newCube) {
_engine->_actor->_previousHeroBehaviour = _engine->_actor->_heroBehaviour;
_engine->_actor->_previousHeroAngle = _sceneHero->_beta;
_engine->autoSave();
}
_engine->_actor->restartPerso();
// StartInitAllObjs
for (int32 a = 1; a < _nbObjets; a++) {
_engine->_actor->startInitObj(a);
}
_engine->_gameState->_nbLittleKeys = 0;
_engine->_gameState->_magicBall = -1;
_engine->_movements->_lastJoyFlag = true;
_engine->_grid->_zoneGrm = -1;
_engine->_grid->_indexGrm = -1;
_engine->_redraw->_firstTime = true;
_engine->_cameraZone = false;
_newCube = SCENE_CEILING_GRID_FADE_1;
_flagChgCube = ScenePositionType::kNoPosition;
_flagRenderGrid = true;
_samplePlayed = 2 * 4 * 8;
_timerNextAmbiance = 0;
ActorStruct *followedActor = getActor(_numObjFollow);
_engine->_grid->centerOnActor(followedActor);
_engine->_screens->_flagFade = true;
_engine->_renderer->setLightVector(_alphaLight, _betaLight, LBAAngles::ANGLE_0);
_zoneHeroPos = IVec3();
debug(2, "Scene %i music track id: %i", _numCube, _cubeJingle);
if (_cubeJingle != 255) {
_engine->_music->playMusic(_cubeJingle);
}
_engine->_gameState->handleLateGameItems();
}
ActorStruct *Scene::getActor(int32 actorIdx) {
if (actorIdx < 0 || actorIdx >= NUM_MAX_ACTORS) {
error("Invalid actor id given: %i", actorIdx);
}
return &_sceneActors[actorIdx];
}
void Scene::initSceneVars() {
_sampleAmbiance[0] = -1;
_sampleAmbiance[1] = -1;
_sampleAmbiance[2] = -1;
_sampleAmbiance[3] = -1;
_sampleRepeat[0] = 0;
_sampleRepeat[1] = 0;
_sampleRepeat[2] = 0;
_sampleRepeat[3] = 0;
_sampleRound[0] = 0;
_sampleRound[1] = 0;
_sampleRound[2] = 0;
_sampleRound[3] = 0;
_nbObjets = 0;
_sceneNumZones = 0;
_sceneNumTracks = 0;
}
void Scene::playSceneMusic() {
if (_engine->isLBA1()) {
if (_numCube == LBA1SceneId::Tippet_Island_Twinsun_Cafe && _engine->_gameState->hasArrivedHamalayi()) {
if (_engine->isCDROM()) {
_engine->_music->playCdTrack(8);
} else {
_engine->_music->playMusic(_cubeJingle);
}
return;
}
}
_engine->_music->playMidiFile(_cubeJingle);
}
void Scene::processEnvironmentSound() {
if (_engine->timerRef < _timerNextAmbiance) {
return;
}
int16 currentAmb = _engine->getRandomNumber(4); // random ambiance
for (int32 s = 0; s < 4; s++) {
if (!(_samplePlayed & (1 << currentAmb))) { // if not already played
_samplePlayed |= (1 << currentAmb); // make sample played
if (_samplePlayed == 15) { // reset if all samples played
_samplePlayed = 0;
}
const int16 sampleIdx = _sampleAmbiance[currentAmb];
if (sampleIdx != -1) {
int16 decal = _sampleRound[currentAmb];
int16 repeat = _sampleRepeat[currentAmb];
const uint16 pitchbend = 0x1000 + _engine->getRandomNumber(decal) - (decal / 2);
_engine->_sound->mixSample(sampleIdx, pitchbend, repeat, 110, 110);
break;
}
}
currentAmb++; // try next ambiance
currentAmb &= 3; // loop in all 4 ambiances
}
// compute next ambiance timer
_timerNextAmbiance = _engine->timerRef + _engine->toSeconds(_engine->getRandomNumber(_sampleMinDelayRnd) + _sampleMinDelay);
}
void Scene::processZoneExtraBonus(ZoneStruct *zone) {
if (zone->infoData.Bonus.used) {
return;
}
const int bonusSprite = _engine->_extra->getBonusSprite(zone->infoData.Bonus.typesFlag);
if (bonusSprite == -1) {
return;
}
const int32 amount = zone->infoData.Bonus.amount;
const int32 x = (zone->maxs.x + zone->mins.x) / 2;
const int32 z = (zone->maxs.z + zone->mins.z) / 2;
const int32 angle = _engine->_movements->getAngle(x, z, _sceneHero->_posObj.x, _sceneHero->_posObj.z);
const int32 index = _engine->_extra->addExtraBonus(x, zone->maxs.y, z, LBAAngles::ANGLE_63, angle, bonusSprite, amount);
if (index != -1) {
_engine->_extra->_extraList[index].type |= ExtraType::TIME_IN;
zone->infoData.Bonus.used = 1; // set as used
}
}
void Scene::checkZoneSce(int32 actorIdx) {
ActorStruct *actor = &_sceneActors[actorIdx];
int32 currentX = actor->_posObj.x;
int32 currentY = actor->_posObj.y;
int32 currentZ = actor->_posObj.z;
actor->_zoneSce = -1;
bool flaggrm = false;
if (IS_HERO(actorIdx)) {
_flagClimbing = false;
}
for (int32 z = 0; z < _sceneNumZones; z++) {
ZoneStruct *zone = &_sceneZones[z];
// check if actor is in zone
if ((currentX >= zone->mins.x && currentX <= zone->maxs.x) &&
(currentY >= zone->mins.y && currentY <= zone->maxs.y) &&
(currentZ >= zone->mins.z && currentZ <= zone->maxs.z)) {
switch (zone->type) {
default:
warning("lba2 zone types not yet implemented");
break;
case ZoneType::kFunFrockFix:
break;
case ZoneType::kCube:
if (IS_HERO(actorIdx) && actor->_lifePoint > 0) {
_newCube = zone->num;
_zoneHeroPos.x = actor->_posObj.x - zone->mins.x + zone->infoData.ChangeScene.x;
_zoneHeroPos.y = actor->_posObj.y - zone->mins.y + zone->infoData.ChangeScene.y;
_zoneHeroPos.z = actor->_posObj.z - zone->mins.z + zone->infoData.ChangeScene.z;
_flagChgCube = ScenePositionType::kZone;
}
break;
case ZoneType::kCamera:
if (_numObjFollow == actorIdx && !_engine->_debugState->_useFreeCamera) {
_engine->_cameraZone = true;
if (_engine->_grid->_startCube.x != zone->infoData.CameraView.x || _engine->_grid->_startCube.y != zone->infoData.CameraView.y || _engine->_grid->_startCube.z != zone->infoData.CameraView.z) {
_engine->_grid->_startCube.x = zone->infoData.CameraView.x;
_engine->_grid->_startCube.y = zone->infoData.CameraView.y;
_engine->_grid->_startCube.z = zone->infoData.CameraView.z;
_engine->_redraw->_firstTime = true;
}
}
break;
case ZoneType::kSceneric:
actor->_zoneSce = zone->num;
break;
case ZoneType::kGrid:
if (_numObjFollow == actorIdx) {
flaggrm = true;
if (_engine->_grid->_zoneGrm != zone->num) {
if (_engine->_grid->_zoneGrm != -1) {
_engine->_grid->copyMapToCube();
}
_engine->_grid->_zoneGrm = zone->num;
_engine->_grid->_indexGrm = z;
_engine->saveTimer(false);
_engine->_grid->initCellingGrid(_engine->_grid->_zoneGrm);
_engine->restoreTimer();
}
}
break;
case ZoneType::kObject:
if (IS_HERO(actorIdx) && _engine->_movements->actionNormal()) {
_engine->_animations->initAnim(AnimationTypes::kAction, AnimType::kAnimationThen, AnimationTypes::kStanding, OWN_ACTOR_SCENE_INDEX);
processZoneExtraBonus(zone);
}
break;
case ZoneType::kText:
if (IS_HERO(actorIdx) && _engine->_movements->actionNormal()) {
_engine->saveTimer(false);
_engine->testRestoreModeSVGA(true);
_engine->_text->setFontCrossColor(zone->infoData.DisplayText.textColor);
_talkingActor = actorIdx;
_engine->_text->drawTextProgressive((TextId)zone->num);
_engine->restoreTimer();
_engine->_redraw->drawScene(true);
}
break;
case ZoneType::kLadder:
if (IS_HERO(actorIdx) && _engine->_actor->_heroBehaviour != HeroBehaviourType::kProtoPack && (actor->_genAnim == AnimationTypes::kForward || actor->_genAnim == AnimationTypes::kTopLadder || actor->_genAnim == AnimationTypes::kClimbLadder)) {
IVec2 destPos = _engine->_renderer->rotate(actor->_boundingBox.mins.x, actor->_boundingBox.mins.z, actor->_beta + LBAAngles::ANGLE_360 + LBAAngles::ANGLE_135);
destPos.x += actor->_processActor.x;
destPos.y += actor->_processActor.z;
if (destPos.x >= 0 && destPos.y >= 0 && destPos.x <= SCENE_SIZE_MAX && destPos.y <= SCENE_SIZE_MAX) {
if (_engine->_grid->worldColBrick(destPos.x, actor->_posObj.y + SIZE_BRICK_Y, destPos.y) != ShapeType::kNone) {
_flagClimbing = true;
if (actor->_posObj.y >= (zone->mins.y + zone->maxs.y) / 2) {
_engine->_animations->initAnim(AnimationTypes::kTopLadder, AnimType::kAnimationAllThen, AnimationTypes::kStanding, actorIdx); // reached end of ladder
} else {
_engine->_animations->initAnim(AnimationTypes::kClimbLadder, AnimType::kAnimationTypeRepeat, AnimationTypes::kNoAnim, actorIdx); // go up in ladder
}
}
}
}
break;
}
}
}
if (!flaggrm && actorIdx == _numObjFollow && _engine->_grid->_zoneGrm != -1) {
_engine->_grid->_zoneGrm = -1;
_engine->_grid->_indexGrm = -1;
_engine->_grid->copyMapToCube();
_engine->_redraw->_firstTime = true;
}
}
void Scene::stopRunningGame() {
free(_currentScene);
_currentScene = nullptr;
}
} // namespace TwinE

251
engines/twine/scene/scene.h Normal file
View File

@@ -0,0 +1,251 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_SCENE_H
#define TWINE_SCENE_SCENE_H
#include "common/scummsys.h"
#include "twine/scene/actor.h"
#include "twine/shared.h"
namespace TwinE {
#define NUM_SCENES_FLAGS 80
#define NUM_MAX_ACTORS 100
#define NUM_MAX_ZONES 100
#define NUM_MAX_TRACKS 200
enum class ScenePositionType {
kNoPosition = 0,
kZone = 1,
kScene = 2,
kReborn = 3
};
// ZONES
#define ZONE_INIT_ON 1
#define ZONE_ON 2
#define ZONE_ACTIVE 4
#define ZONE_OBLIGATOIRE 8
/**
* Special actions, like change scene, climbing a ladder, ...
*/
struct ZoneStruct {
IVec3 mins;
IVec3 maxs;
ZoneType type = ZoneType::kCube;
int32 num;
union {
struct {
int32 x;
int32 y;
int32 z;
} ChangeScene;
struct {
int32 x;
int32 y;
int32 z;
} CameraView;
/** show a text (e.g. when reading a sign) */
struct {
int32 textColor; /*!< text color (see @c ActorStruct::talkColor) */
} DisplayText;
struct {
BonusParameter typesFlag;
int32 amount;
/**
* Already used
*/
int32 used;
} Bonus;
struct {
int32 info0;
int32 info1;
int32 info2;
int32 info3;
int32 info4;
int32 info5;
int32 info6;
int32 info7;
} generic;
} infoData;
};
class TwinEEngine;
/**
* scene 0: 23 actors
*
* scene 1: 14 actors
* actor 1 - car
* actor 2 - elephant
* actor 3 - soldier at the house
* actor 4 - patrolling soldier before gate
* actor 5 - soldier after gate
* actor 6 - ??
* actor 7 - ??
* actor 8 - left gate
* actor 9 - ??
* actor 10 - door after leaving truck
* actor 11 - door subway
* actor 12 - guy at rubbish
* actor 13 - ??
*/
class Scene {
private:
TwinEEngine *_engine;
void loadModel(ActorStruct &actor, int32 modelIndex, bool lba1);
/** Process zone extra bonus */
void processZoneExtraBonus(ZoneStruct *zone);
void setActorStaticFlags(ActorStruct *act, uint32 staticFlags);
void setBonusParameterFlags(ActorStruct *act, uint16 bonusFlags);
bool loadSceneLBA1();
bool loadSceneLBA2();
/** Initialize new scene */
bool loadScene(int32 index);
/** Reset scene */
void clearScene();
// the first actor is the own hero
ActorStruct _sceneActors[NUM_MAX_ACTORS]; // ListObjet
int32 _currentSceneSize = 0;
bool _isOutsideScene = false; // lba2
/** Timer for the next sample ambience in scene */
int32 _timerNextAmbiance = 0;
int16 _sampleAmbiance[4]{0};
int16 _sampleRepeat[4]{0};
int16 _sampleRound[4]{0};
int16 _sampleFrequency[4]{0}; // lba2
int16 _sampleVolume[4]{0}; // lba2
int16 _sampleMinDelay = 0; // SecondMin
int16 _sampleMinDelayRnd = 0; // SecondEcart
int16 _samplePlayed = 0;
public:
int16 _cubeJingle = 0;
private:
IVec3 _sceneHeroPos; // CubeStartX, CubeStartY, CubeStartZ
IVec3 _zoneHeroPos; // NewPosX, NewPosY, NewPosZ
int32 _currentGameOverScene = 0;
uint8 *_currentScene = nullptr;
void dumpSceneScripts() const;
void dumpSceneScript(const char *type, int actorIdx, const uint8* script, int size) const;
public:
Scene(TwinEEngine *engine) : _engine(engine) {}
~Scene();
int32 _newCube = LBA1SceneId::Citadel_Island_Prison;
int32 _numCube = LBA1SceneId::Citadel_Island_Prison; // NumCube
int32 _oldcube = LBA1SceneId::Citadel_Island_Prison;
int32 _planet = -1;
int32 _numHolomapTraj = -1;
TextBankId _sceneTextBank = TextBankId::None;
int32 _alphaLight = 0;
int32 _betaLight = 0;
uint8 _island = 0;
uint8 _shadowLevel = 0; // lba2
uint8 _modeLabyrinthe = 0; // lba2
uint8 _cinemaMode = 0; // lba2
uint8 _currentCubeX = 0; // lba2
uint8 _currentCubeY = 0; // lba2
IVec3 _sceneStart;
/** Hero Y coordinate before fall */
int16 _startYFalling = 0;
/** Hero type of position in scene */
ScenePositionType _flagChgCube = ScenePositionType::kNoPosition; // twinsenPositionModeInNewCube
// ACTORS
int32 _nbObjets = 0;
ActorStruct *_sceneHero = nullptr;
/** Meca penguin actor index */
int16 _mecaPenguinIdx = 0;
/** Current followed actor in scene */
int16 _numObjFollow = OWN_ACTOR_SCENE_INDEX;
/** Current actor in zone - climbing a ladder */
bool _flagClimbing = false;
/** Current actor manipulated in scripts */
int16 _currentScriptValue = 0;
int16 _talkingActor = 0;
// TRACKS Tell the actor where to go
int32 _sceneNumTracks = 0;
IVec3 _sceneTracks[NUM_MAX_TRACKS];
bool _flagRenderGrid = true; // FlagAffGrille
uint8 _listFlagCube[NUM_SCENES_FLAGS]{0}; // ListVarCube
int32 _sceneNumZones = 0; // NbZones
ZoneStruct _sceneZones[NUM_MAX_ZONES]; // ListZone
ActorStruct *getActor(int32 actorIdx); // ListObjet
void playSceneMusic();
void reloadCurrentScene();
/** Change to another scene */
void changeCube();
/** For the buggy to get the 2D coordinates of an exterior cube in the map */
bool loadSceneCubeXY(int sceneNum, int32 *cubeX, int32 *cubeY);
/** Process scene environment sound */
void processEnvironmentSound(); // GereAmbiance
void initSceneVars();
bool isGameRunning() const;
void stopRunningGame();
/**
* Process actor zones
* @param actorIdx Process actor index
*/
void checkZoneSce(int32 actorIdx);
};
inline bool Scene::isGameRunning() const {
return _currentScene != nullptr;
}
} // namespace TwinE
#endif

View File

@@ -0,0 +1,42 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "twine/scene/wagon.h"
namespace TwinE {
void Wagon::DoAnimWagon() {
}
void Wagon::DoDirWagon(ActorStruct *ptrobj) {
}
int32 Wagon::GetNumBrickWagon(int32 brick) {
return -1;
}
void Wagon::AdjustEssieuWagonAvant(int32 brickw) {
}
void Wagon::AdjustEssieuWagonArriere(int32 brickw) {
}
} // namespace TwinE

View File

@@ -0,0 +1,46 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef TWINE_SCENE_WAGON_H
#define TWINE_SCENE_WAGON_H
#include "twine/scene/actor.h"
#include "twine/twine.h"
namespace TwinE {
class Wagon {
private:
//TwinEEngine *_engine;
public:
Wagon(TwinEEngine *engine) /* : _engine(engine) */ {}
void DoAnimWagon();
void DoDirWagon(ActorStruct *ptrobj);
int32 GetNumBrickWagon(int32 brick);
void AdjustEssieuWagonAvant(int32 brickw);
void AdjustEssieuWagonArriere(int32 brickw);
};
} // namespace TwinE
#endif