Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.
Layout:
engine/ MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
models) + image codec; the minimal rp/ headers the audio HAL needs
game/ reconstructed BT logic + surviving-original BT source + fwd shims
+ WinMain launcher
content/ full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
docs/ format specs + reconstruction ledgers
reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
tools/ MP console emulator + map/resource scanners
One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
621 lines
14 KiB
C++
621 lines
14 KiB
C++
#pragma once
|
|
|
|
#include "simulate.h"
|
|
#include "origin.h"
|
|
#include "linmtrx.h"
|
|
|
|
class Player;
|
|
class NotationFile;
|
|
class DamageZone;
|
|
class Damage;
|
|
class Subsystem;
|
|
|
|
#define ENTITY_CONTINUATION
|
|
#include "entity2.h"
|
|
|
|
//##########################################################################
|
|
//########################### Entity #################################
|
|
//##########################################################################
|
|
|
|
class Entity:
|
|
public Simulation
|
|
{
|
|
friend class Entity__StaticVideoSocketIterator;
|
|
friend class Entity__DynamicVideoSocketIterator;
|
|
friend class Entity__AudioSocketIterator;
|
|
friend class Entity__GaugeSocketIterator;
|
|
friend class Entity__AudioLocationIterator;
|
|
|
|
//##########################################################################
|
|
// Shared Data support
|
|
//
|
|
public:
|
|
typedef Entity__SharedData SharedData;
|
|
SharedData*
|
|
GetSharedData();
|
|
|
|
static Derivation *GetClassDerivations();
|
|
|
|
static SharedData DefaultData;
|
|
|
|
//##########################################################################
|
|
// Message Support
|
|
//
|
|
public:
|
|
enum {
|
|
MakeMessageID = Simulation::NextMessageID,
|
|
MakeReadyMessageID,
|
|
RemakeEntityMessageID,
|
|
RemakeReadyMessageID,
|
|
RemakeCompleteMessageID,
|
|
RemakeIncompleteMessageID,
|
|
DestroyEntityMessageID,
|
|
TransferEntityMessageID,
|
|
TransferCompleteMessageID,
|
|
TransferIncompleteMessageID,
|
|
BecomeInterestingMessageID,
|
|
BecomeUninterestingMessageID,
|
|
UpdateMessageID,
|
|
SubscribeReplicantMessageID,
|
|
UnsubscribeReplicantMessageID,
|
|
TakeDamageMessageID,
|
|
TakeDamageStreamMessageID,
|
|
PlayerLinkMessageID,
|
|
NextMessageID
|
|
};
|
|
|
|
typedef Entity__Message Message;
|
|
typedef Entity__MakeMessage MakeMessage;
|
|
typedef Entity__MakeReadyMessage MakeReadyMessage;
|
|
typedef Entity__RemakeEntityMessage RemakeEntityMessage;
|
|
typedef Entity__RemakeReadyMessage RemakeReadyMessage;
|
|
typedef Entity__RemakeCompleteMessage RemakeCompleteMessage;
|
|
typedef Entity__RemakeIncompleteMessage RemakeIncompleteMessage;
|
|
typedef Entity__DestroyEntityMessage DestroyEntityMessage;
|
|
typedef Entity__TransferEntityMessage TransferEntityMessage;
|
|
typedef Entity__TransferCompleteMessage TransferCompleteMessage;
|
|
typedef Entity__TransferIncompleteMessage TransferIncompleteMessage;
|
|
typedef Entity__BecomeInterestingMessage BecomeInterestingMessage;
|
|
typedef Entity__BecomeUninterestingMessage BecomeUninterestingMessage;
|
|
typedef Entity__UpdateMessage UpdateMessage;
|
|
typedef Entity__SubscribeReplicantMessage SubscribeReplicantMessage;
|
|
typedef Entity__UnsubscribeReplicantMessage UnsubscribeReplicantMessage;
|
|
typedef Entity__TakeDamageMessage TakeDamageMessage;
|
|
typedef Entity__TakeDamageStreamMessage TakeDamageStreamMessage;
|
|
typedef Entity__PlayerLinkMessage PlayerLinkMessage;
|
|
typedef Entity__DynamicMessage DynamicMessage;
|
|
typedef Entity__MakeMapMessage MakeMapMessage;
|
|
|
|
|
|
void
|
|
Receive(Event *event);
|
|
|
|
void
|
|
LocalDispatch(Message *what)
|
|
{Receiver::Receive(Cast_Object(Receiver::Message*,what));}
|
|
|
|
void
|
|
Dispatch(Receiver::Message *what);
|
|
|
|
void
|
|
DispatchToReplicant(
|
|
Message *what,
|
|
HostID host
|
|
);
|
|
|
|
void
|
|
DispatchToReplicants(Message *what);
|
|
|
|
private:
|
|
static const HandlerEntry MessageHandlerEntries[];
|
|
|
|
protected:
|
|
//static MessageHandlerSet MessageHandlers;
|
|
static MessageHandlerSet& GetMessageHandlers();
|
|
|
|
//##########################################################################
|
|
// Attribute Support
|
|
//
|
|
public:
|
|
enum {
|
|
LocalToWorldAttributeID = Simulation::NextAttributeID,
|
|
LocalOriginAttributeID,
|
|
DamageZoneCountAttributeID,
|
|
DamageZonesAttributeID,
|
|
NextAttributeID
|
|
};
|
|
|
|
private:
|
|
static const IndexEntry AttributePointers[];
|
|
|
|
protected:
|
|
//static AttributeIndexSet AttributeIndex
|
|
static AttributeIndexSet& GetAttributeIndex();
|
|
|
|
//
|
|
// public attribute declarations go here
|
|
//
|
|
public:
|
|
LinearMatrix localToWorld;
|
|
Origin localOrigin;
|
|
int damageZoneCount;
|
|
DamageZone **damageZones;
|
|
|
|
|
|
int
|
|
GetDamageZoneIndex(const CString &damage_zone_name) const;
|
|
|
|
|
|
//
|
|
// virtual access
|
|
//
|
|
public:
|
|
virtual Vector3D
|
|
GetWorldLinearVelocity()
|
|
{return Vector3D(0.0f, 0.0f, 0.0f);}
|
|
virtual Vector3D
|
|
GetWorldLinearAcceleration()
|
|
{return Vector3D(0.0f, 0.0f, 0.0f);}
|
|
|
|
//##########################################################################
|
|
// Model Support
|
|
//
|
|
public:
|
|
UpdateMessage*
|
|
Execute(const Time &till);
|
|
|
|
Subsystem*
|
|
GetSubsystem(int index)
|
|
{
|
|
Check(this); Verify((unsigned)index < subsystemCount);
|
|
return subsystemArray[index];
|
|
}
|
|
Simulation*
|
|
GetSimulation(int index);
|
|
int
|
|
GetSubsystemCount()
|
|
{Check(this); return subsystemCount;}
|
|
Subsystem*
|
|
FindSubsystem(const char* name);
|
|
|
|
typedef void
|
|
(Entity::*Performance)(Scalar time_slice);
|
|
|
|
typedef Entity__UpdateRecord UpdateRecord;
|
|
|
|
void
|
|
SetPerformance(Performance performance)
|
|
{
|
|
Check(this);
|
|
activePerformance = (Simulation::Performance)performance;
|
|
}
|
|
|
|
enum {
|
|
EntitySubsystemID = -1
|
|
};
|
|
|
|
static int
|
|
FindSubsytemID(
|
|
const char *model_name,
|
|
const char *subsystem_name
|
|
);
|
|
public:
|
|
|
|
enum {
|
|
DamageZoneUpdateModelBit = Simulation::NextUpdateModelBit,
|
|
NextUpdateModelBit
|
|
};
|
|
|
|
enum {
|
|
DamageZoneUpdateModelFlag = 1 << DamageZoneUpdateModelBit
|
|
};
|
|
|
|
Logical DamageZoneUpdateModelFlagSet()
|
|
{
|
|
Check(this);
|
|
return ((updateModel & DamageZoneUpdateModelFlag) != 0);
|
|
}
|
|
|
|
protected:
|
|
|
|
void
|
|
WriteUpdateRecord(
|
|
Simulation::UpdateRecord *message,
|
|
int update_model
|
|
);
|
|
void
|
|
UpdateMessageHandler(UpdateMessage *message);
|
|
|
|
void
|
|
ReadUpdateRecord(Simulation::UpdateRecord *message);
|
|
|
|
void
|
|
ReadDamageUpdateRecord(Simulation::UpdateRecord *update_record);
|
|
|
|
void
|
|
WriteDamageUpdateRecord(Simulation::UpdateRecord *update_record);
|
|
|
|
void
|
|
PerformAndWatch(
|
|
const Time &till,
|
|
MemoryStream *update_stream
|
|
);
|
|
|
|
int
|
|
subsystemCount;
|
|
Subsystem
|
|
**subsystemArray;
|
|
Origin
|
|
updateOrigin;
|
|
|
|
void
|
|
TakeDamageStreamMessageHandler(TakeDamageStreamMessage *message);
|
|
void
|
|
TakeDamageMessageHandler(TakeDamageMessage *message);
|
|
void
|
|
PlayerLinkMessageHandler(PlayerLinkMessage *message);
|
|
|
|
//##########################################################################
|
|
// Renderer Support
|
|
//
|
|
public:
|
|
void
|
|
AddStaticVideoComponent(Component *component);
|
|
void
|
|
AddDynamicVideoComponent(Component *component);
|
|
void
|
|
AddAudioComponent(Component *component);
|
|
|
|
typedef Entity__StaticVideoSocketIterator
|
|
StaticVideoSocketIterator;
|
|
typedef Entity__DynamicVideoSocketIterator
|
|
DynamicVideoSocketIterator;
|
|
typedef Entity__AudioSocketIterator
|
|
AudioSocketIterator;
|
|
|
|
virtual Enumeration
|
|
GetAudioRepresentation(Entity *linked_entity);
|
|
|
|
private:
|
|
SChainOf<Component*>
|
|
staticVideoSocket;
|
|
SChainOf<Component*>
|
|
dynamicVideoSocket;
|
|
SChainOf<Component*>
|
|
audioSocket;
|
|
|
|
//##########################################################################
|
|
// Flag Support
|
|
//
|
|
public:
|
|
//
|
|
// MasterInstance - created on owning host, replicated on remote hosts,
|
|
// sends updates to replicants
|
|
// ReplicantInstance - created on remote hosts, receives updates from
|
|
// MasterInstance
|
|
// IndependantInstance - created on owning host, replicated on remote
|
|
// hosts, never receives updates from MasterInstance
|
|
// HermitInstance - created on owning host, is not replicated, does
|
|
// send updates
|
|
//
|
|
enum {
|
|
InstanceBits = Simulation::NextBit,
|
|
ValidBit = InstanceBits+2,
|
|
TransferableBit,
|
|
InterestBit,
|
|
InterestLockedBit,
|
|
DynamicBit,
|
|
TrappedBit,
|
|
StatueBit,
|
|
MapBit,
|
|
PreRunBit,
|
|
CondemnedBit,
|
|
NextBit
|
|
};
|
|
enum Instance
|
|
{
|
|
MasterInstance=0,
|
|
ReplicantInstance=1<<InstanceBits,
|
|
IndependantInstance=2<<InstanceBits,
|
|
HermitInstance=3<<InstanceBits
|
|
};
|
|
enum {
|
|
InstanceMask = HermitInstance,
|
|
ValidFlag = 1<<ValidBit,
|
|
TransferableFlag = 1<<TransferableBit,
|
|
InterestFlag = 1<<InterestBit,
|
|
InterestLockedFlag = 1<<InterestLockedBit,
|
|
DynamicFlag = 1<<DynamicBit,
|
|
TrappedFlag = 1<<TrappedBit,
|
|
StatueFlag = 1<<StatueBit,
|
|
MapFlag = 1<<MapBit,
|
|
PreRunFlag = 1<<PreRunBit,
|
|
CondemnedFlag = 1<<CondemnedBit,
|
|
TypeMask = DynamicFlag|TrappedFlag|MapFlag,
|
|
// DefaultFlags = MasterInstance
|
|
DefaultFlags = DynamicFlag|MasterInstance
|
|
};
|
|
enum Type {
|
|
StaticType = 0,
|
|
DynamicType = DynamicFlag,
|
|
TrappedType = DynamicFlag|TrappedFlag,
|
|
StatueType = DynamicFlag|TrappedFlag|StatueFlag,
|
|
MapType = DynamicFlag|MapFlag,
|
|
TrappedMapType = DynamicFlag|TrappedFlag|MapFlag,
|
|
};
|
|
|
|
//##########################################################################
|
|
// Instance Type support
|
|
//
|
|
public:
|
|
void
|
|
SetInstance(Instance type)
|
|
{Check(this); simulationFlags &= ~InstanceMask; simulationFlags |= type;}
|
|
Instance
|
|
GetInstance()
|
|
{Check(this); return (Instance)(simulationFlags&InstanceMask);}
|
|
|
|
static LWord
|
|
EntityFlagsSetInstance(LWord entity_flags, Instance type)
|
|
{
|
|
entity_flags &= ~InstanceMask;
|
|
entity_flags |= type;
|
|
return entity_flags;
|
|
}
|
|
static Instance
|
|
EntityFlagsGetInstance(LWord entity_flags)
|
|
{return (Instance)(entity_flags&InstanceMask);}
|
|
|
|
//##########################################################################
|
|
// Validity Support
|
|
//
|
|
public:
|
|
void
|
|
SetValidFlag()
|
|
{Check(this); simulationFlags |= ValidFlag;}
|
|
void
|
|
SetInvalidFlag()
|
|
{Check(this); simulationFlags &= ~ValidFlag;}
|
|
Logical
|
|
IsValid()
|
|
{Check(this); return (simulationFlags&ValidFlag) != 0;}
|
|
|
|
void
|
|
SetPreRunFlag()
|
|
{Check(this); simulationFlags |= PreRunFlag;}
|
|
void
|
|
SetNoPreRunFlag()
|
|
{Check(this); simulationFlags &= ~PreRunFlag;}
|
|
Logical
|
|
IsPreRunnable()
|
|
{Check(this); return (simulationFlags&PreRunFlag) != 0;}
|
|
|
|
void
|
|
SetCondemnedFlag()
|
|
{Check(this); simulationFlags |= CondemnedFlag;}
|
|
Logical
|
|
IsCondemned()
|
|
{Check(this); return (simulationFlags&CondemnedFlag) != 0;}
|
|
|
|
//##########################################################################
|
|
// Transference Support
|
|
//
|
|
public:
|
|
void
|
|
SetTransferableFlag()
|
|
{Check(this); simulationFlags |= TransferableFlag;}
|
|
void
|
|
SetNontransferableFlag()
|
|
{Check(this); simulationFlags &= ~TransferableFlag;}
|
|
Logical
|
|
IsTransferable()
|
|
{Check(this); return (simulationFlags&TransferableFlag) != 0;}
|
|
|
|
const EntityID&
|
|
GetEntityID()
|
|
{return entityID;}
|
|
HostID
|
|
GetOwnerID()
|
|
{return ownerID;}
|
|
|
|
EntityID
|
|
entityID;
|
|
HostID
|
|
ownerID;
|
|
|
|
Player
|
|
*playerLink;
|
|
|
|
Player*
|
|
GetPlayerLink()
|
|
{return playerLink;}
|
|
|
|
//##########################################################################
|
|
// Interest Support
|
|
//
|
|
public:
|
|
void
|
|
BecomeInteresting();
|
|
void
|
|
BecomeInterestingMessageHandler(const Receiver::Message*);
|
|
void
|
|
BecomeUninterestingMessageHandler();
|
|
|
|
Logical
|
|
IsInteresting()
|
|
{Check(this); return interestCount != 0;}
|
|
|
|
void
|
|
SetInterestLockedFlag();
|
|
void
|
|
SetInterestUnlockedFlag();
|
|
Logical
|
|
IsInterestLocked()
|
|
{Check(this); return (simulationFlags&InterestLockedFlag) != 0;}
|
|
|
|
void
|
|
SetInterestZoneID(InterestZoneID interest_zone_ID)
|
|
{interestZoneID = interest_zone_ID;}
|
|
InterestZoneID
|
|
GetInterestZoneID()
|
|
{return interestZoneID;}
|
|
|
|
Logical
|
|
IsStatic()
|
|
{Check(this); return (simulationFlags&DynamicFlag) == 0;}
|
|
Logical
|
|
IsDynamic()
|
|
{Check(this); return (simulationFlags&DynamicFlag) != 0;}
|
|
|
|
void
|
|
SetTrappedFlag()
|
|
{Check(this); simulationFlags |= TrappedFlag;}
|
|
Logical
|
|
IsTrapped()
|
|
{Check(this); return (simulationFlags&TrappedFlag) != 0;}
|
|
|
|
Logical
|
|
IsStatue()
|
|
{Check(this); return (simulationFlags&StatueFlag) != 0;}
|
|
|
|
Logical
|
|
IsMap()
|
|
{Check(this); return (simulationFlags&MapFlag) != 0;}
|
|
|
|
static Logical
|
|
EntityFlagsIsMap(LWord entity_flags)
|
|
{return (entity_flags&MapFlag) != 0;}
|
|
|
|
Type
|
|
GetType()
|
|
{Check(this); return (Type)(simulationFlags&TypeMask);}
|
|
|
|
virtual Enumeration
|
|
GetInterestPriority(Entity *linked_entity);
|
|
|
|
InterestZoneID
|
|
interestZoneID;
|
|
int
|
|
interestCount;
|
|
Time
|
|
creationTime;
|
|
|
|
//##########################################################################
|
|
// Camera Support
|
|
//
|
|
protected:
|
|
|
|
Origin
|
|
cameraOffset;
|
|
|
|
public:
|
|
Origin
|
|
GetCameraOffset() const
|
|
{Check(this); return cameraOffset; }
|
|
|
|
//##########################################################################
|
|
// Scoring support
|
|
//
|
|
public:
|
|
//
|
|
// HACK - ECH 7/6/95 - Allow the player vehicle to respond to score
|
|
// messages, allows attribute system to be used for scoring
|
|
//
|
|
virtual void
|
|
RespondToScoreMessage(Message *message) {}
|
|
|
|
//##########################################################################
|
|
// Construction and Destruction
|
|
//
|
|
public:
|
|
typedef Entity* (*MakeHandler)(MakeMessage *);
|
|
|
|
static Entity*
|
|
Make(MakeMessage *creation_message);
|
|
|
|
//
|
|
// Warning... This function requires a properly set up strtok!!!!
|
|
//
|
|
static Logical
|
|
CreateMakeMessage(
|
|
MakeMessage *creation_message,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
static Logical
|
|
CreateMakeMapMessage(
|
|
MakeMapMessage *creation_message,
|
|
ResourceDescription::ResourceID resource_id,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories * //directories
|
|
);
|
|
|
|
static ResourceDescription::ResourceID
|
|
CreateDamageZoneStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
static ResourceDescription::ResourceID
|
|
CreateExplosionTableStream(
|
|
ResourceFile *resource_file,
|
|
const char *model_name,
|
|
NotationFile *model_file,
|
|
const ResourceDirectories *directories
|
|
);
|
|
|
|
ResourceDescription::ResourceID
|
|
GetResourceID()
|
|
{Check(this); return resourceID;}
|
|
Player*
|
|
GetOwningPlayer()
|
|
{Check(this); return owningPlayer;}
|
|
|
|
void
|
|
SetupNetworkMessage(Entity::Message *message);
|
|
|
|
protected:
|
|
Entity(
|
|
MakeMessage *creation_message,
|
|
SharedData &virtual_data
|
|
);
|
|
|
|
ResourceDescription::ResourceID
|
|
resourceID;
|
|
Player
|
|
*owningPlayer;
|
|
|
|
void
|
|
DestroyEntityMessageHandler(Message *message);
|
|
|
|
public:
|
|
~Entity();
|
|
|
|
void
|
|
CondemnToDeathRow();
|
|
|
|
//##########################################################################
|
|
// Test Support
|
|
//
|
|
public:
|
|
Logical
|
|
TestInstance() const;
|
|
static Logical
|
|
TestClass();
|
|
};
|
|
|
|
inline void
|
|
Entity::SetupNetworkMessage(Entity::Message *message)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
message->entityID = entityID;
|
|
message->interestZoneID = interestZoneID;
|
|
message->ownerID = ownerID;
|
|
}
|
|
|
|
#include "entity3.h"
|
|
#undef ENTITY_CONTINUATION |