Files
BT411/engine/MUNGA/DROPZONE.cpp
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
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>
2026-07-05 21:03:40 -05:00

448 lines
11 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "dropzone.h"
#include "random.h"
#include "app.h"
#include "hostmgr.h"
#include "nttmgr.h"
#include "notation.h"
#include "namelist.h"
#include "player.h"
#define DOWN_TIME 5.0f
//#############################################################################
//############################### DropZone #################################
//#############################################################################
//#############################################################################
// Shared Data Support
//
Derivation* DropZone::GetClassDerivations()
{
static Derivation classDerivations(Entity::GetClassDerivations(),"DropZone");
return &classDerivations;
}
DropZone::SharedData
DropZone::DefaultData(
DropZone::GetClassDerivations(),
DropZone::GetMessageHandlers(),
DropZone::GetAttributeIndex(),
DropZone::StateCount,
(Entity::MakeHandler)DropZone::Make
);
//#############################################################################
// Message Support
//
const Receiver::HandlerEntry
DropZone::MessageHandlerEntries[]=
{
MESSAGE_ENTRY(DropZone, AssignDropZone)
};
Receiver::MessageHandlerSet& DropZone::GetMessageHandlers()
{
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(DropZone::MessageHandlerEntries), DropZone::MessageHandlerEntries, Entity::GetMessageHandlers());
return messageHandlers;
}
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone::DropZone(
DropZone::MakeMessage *creation_message,
DropZone::SharedData &virtual_data
):
Entity(creation_message, virtual_data)
{
Check_Pointer(this);
Check_Pointer(creation_message);
SetValidFlag();
dropZoneCount = 0;
dropZones = NULL;
//
//--------------------------------------------------------------
// Name the drop zone, and enter the zone in the drop zone group
//--------------------------------------------------------------
//
Str_Copy(dropZoneName, creation_message->dropZoneName, sizeof(dropZoneName));
EntityManager *entity_manager = application->GetEntityManager();
Check(entity_manager);
EntityGroup* drop_zones = entity_manager->UseGroup("DropZones");
Check(drop_zones);
drop_zones->Add(this);
//
//-----------------------------------------------------------------------
// Now, interpret the data to create the appropriate number of drop zones
//-----------------------------------------------------------------------
//
MemoryStream
stream(
creation_message,
creation_message->messageLength,
sizeof(*creation_message)
);
dropZoneCount = creation_message->dropZoneCount;
Verify(dropZoneCount > 0);
dropZones = new Origin[dropZoneCount];
Register_Pointer(dropZones);
lastUsageTime = new Time[dropZoneCount];
Register_Pointer(lastUsageTime);
lastUsedBy = new EntityID[dropZoneCount];
Register_Pointer(lastUsedBy);
lastDeathCount = new int[dropZoneCount];
Register_Pointer(lastDeathCount);
for (int i=0; i<dropZoneCount; ++i)
{
dropZones[i] = *(Origin*)stream.GetPointer();
lastUsageTime[i] = Time::Null;
lastUsedBy[i] = EntityID::Null;
lastDeathCount[i] = -3;
stream.AdvancePointer(sizeof(Origin));
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone*
DropZone::Make(DropZone::MakeMessage *creation_message)
{
return new DropZone(creation_message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
DropZone::~DropZone()
{
if (dropZones)
{
Unregister_Pointer(dropZones);
delete[] dropZones;
Unregister_Pointer(lastUsageTime);
delete[] lastUsageTime;
Unregister_Pointer(lastUsedBy);
delete[] lastUsedBy;
Unregister_Pointer(lastDeathCount);
delete[] lastDeathCount;
}
}
//#############################################################################
// Dropzone assignment
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
DropZone::IsAvailable(int index)
{
Check(this);
Verify(index >= 0 && index < dropZoneCount);
Check(application);
return
!lastUsageTime[index].ticks
|| Now() - lastUsageTime[index] > DOWN_TIME
&& application->GetApplicationState() == Application::RunningMission;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
DropZone::AssignDropZoneMessageHandler(AssignDropZoneMessage* message)
{
Check(this);
Check_Pointer(message);
Check(application);
HostManager *host = application->GetHostManager();
Check(host);
Entity *entity = host->GetEntityPointer(message->requestingEntity);
Check(entity);
//
//---------------------------------------------------------------------
// If we have allocated a dropzone within the last ten seconds for this
// player, resend the data
//---------------------------------------------------------------------
//
Origin *drop_zone = NULL;
int
lowest, highest, remaining, i;
for (i=0; i<dropZoneCount; ++i)
{
if (
!IsAvailable(i)
&& lastUsedBy[i] == message->requestingEntity
&& lastDeathCount[i] == message->deathCount
)
{
lastUsageTime[i] = Now();
drop_zone = &dropZones[i];
goto Found_One;
}
}
//
//------------------------------------------------------------------------
// Figure out how many drop zones are available. If none are, repost this
// message to ourself
//------------------------------------------------------------------------
//
lowest = dropZoneCount;
highest = -1;
remaining = 0;
for (i=0; i<dropZoneCount; ++i)
{
if (IsAvailable(i))
{
++remaining;
if (i < lowest)
{
lowest = i;
}
if (i > highest)
{
highest = i;
}
}
}
//
//-----------------------------------------------------
// Now, randomly look about until we find one available
//-----------------------------------------------------
//
highest = highest - lowest + 1;
while (remaining)
{
i = lowest + Random(highest);
Verify(i < dropZoneCount && i >= 0);
if (IsAvailable(i))
{
//
//-------------------------------------------------------------------
// Check to see if vehicle attached to a player is within 2 meters of
// this drop zone. If so, mark it unavailable for 3 seconds
//-------------------------------------------------------------------
//
EntityManager *entity_mgr = application->GetEntityManager();
Check(entity_mgr);
EntityGroup *player_group = entity_mgr->FindGroup("Players");
if (
player_group
&& application->GetApplicationState()
== Application::RunningMission
)
{
Player *player;
ChainIteratorOf<Node*> iterator(player_group->groupMembers);
while ((player = (Player*)iterator.ReadAndNext()) != NULL)
{
Check(player);
Entity *vehicle = player->GetPlayerVehicle();
if (vehicle && player != entity)
{
Check(vehicle);
Vector3D distance;
distance.Subtract(
vehicle->localOrigin.linearPosition,
dropZones[i].linearPosition
);
if (distance.LengthSquared() <= 4.0f)
{
lastUsedBy[i] = NULL;
lastUsageTime[i] = Now();
lastUsageTime[i] -= DOWN_TIME - 1.0f;
--remaining;
if (i == lowest)
{
++lowest;
--highest;
}
else if (i == lowest + highest - 1)
{
--highest;
}
break;
}
}
}
//
//----------------------------------------------------------
// If someone was in this spot, keep looking for another one
//----------------------------------------------------------
//
if (player)
{
continue;
}
}
lastUsageTime[i] = Now();
drop_zone = &dropZones[i];
lastUsedBy[i] = message->requestingEntity;
lastDeathCount[i] = message->deathCount;
remaining = 0;
}
}
//
//----------------------------------------------------------------
// If no drop_zone could be found, repost the message to ourselves
//----------------------------------------------------------------
//
if (!drop_zone)
{
Time when=Now();
when += 0.1f;
application->Post(MaxEventPriority, this, message, when);
return;
}
//
//--------------------------------------------------------------------
// Send a message back to the requesting object what sent this message
//--------------------------------------------------------------------
//
Found_One:
ReplyMessage
reply(
message->replyMessageID,
sizeof(ReplyMessage),
*drop_zone,
message->deathCount
);
entity->Dispatch(&reply);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
DropZone::CreateMakeMessage(
MakeMessage *creation_message,
const char* model_name,
const char* resource_name,
NotationFile *map_file
)
{
Check(creation_message);
Check(map_file);
Check_Pointer(model_name);
Check_Pointer(resource_name);
if (!Entity::CreateMakeMessage(creation_message, map_file, NULL))
{
return False;
}
creation_message->classToCreate = RegisteredClass::DropZoneClassID;
creation_message->instanceFlags = MapFlag|TrappedFlag;
//
//-----------------------------------------------
// Count up the number of drop zones in our model
//-----------------------------------------------
//
Enumeration save_modes = map_file->GetModes();
map_file->SetMode(NotationFile::CleanListMode);
NameList *zone_list =
map_file->MakeEntryList(model_name, "dropzone");
Register_Object(zone_list);
creation_message->dropZoneCount = zone_list->EntryCount();
creation_message->messageLength =
sizeof(*creation_message)
+ creation_message->dropZoneCount*sizeof(Origin);
if (!creation_message->dropZoneCount)
{
std::cout << "Error: " << model_name << " hasn't specified any drop zones!\n";
Dump_And_Die:
Unregister_Object(zone_list);
delete zone_list;
map_file->PutModes(save_modes);
return False;
}
//
//---------------------------------------------
// Allocate a buffer to hold the drop zone data
//---------------------------------------------
//
MemoryStream
stream(
creation_message,
creation_message->messageLength,
sizeof(*creation_message)
);
//
//----------------------------
// zero out the drop zone name
//----------------------------
//
for (int ii=0; ii<sizeof(creation_message->dropZoneName); ii++)
{
creation_message->dropZoneName[ii] = '\0';
}
Str_Copy(
creation_message->dropZoneName,
resource_name,
sizeof(creation_message->dropZoneName)
);
//
//-----------------
// Read in the data
//-----------------
//
NameList::Entry *drop_zone = zone_list->GetFirstEntry();
while (drop_zone)
{
Point3D translation;
EulerAngles tmp_rotation;
const char* zone_data = drop_zone->GetChar();
sscanf(
zone_data,
"%f %f %f %f %f %f",
&translation.x,
&translation.y,
&translation.z,
&tmp_rotation.pitch,
&tmp_rotation.yaw,
&tmp_rotation.roll
);
((Origin*)stream.GetPointer())->linearPosition = translation;
((Origin*)stream.GetPointer())->angularPosition = tmp_rotation;
stream.AdvancePointer(sizeof(Origin));
drop_zone = drop_zone->GetNextEntry();
}
Unregister_Object(zone_list);
delete zone_list;
map_file->PutModes(save_modes);
return True;
}
Logical
DropZone::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}