From dffc784fa79f77dc98dc4b1fc84ad2c96817d189 Mon Sep 17 00:00:00 2001 From: Jonathan Wilson Date: Sun, 16 Apr 2023 15:11:34 +1000 Subject: [PATCH] Implement some of TerrainLogic --- src/CMakeLists.txt | 3 + src/game/client/terrainroads.h | 3 +- src/game/common/mapobject.cpp | 7 +- src/game/common/mapobject.h | 5 +- src/game/common/staticnamekey.cpp | 4 + src/game/common/staticnamekey.h | 4 + src/game/common/system/geometry.h | 1 + src/game/logic/ai/aipathfind.cpp | 32 + src/game/logic/ai/aipathfind.h | 5 + src/game/logic/map/terrainlogic.cpp | 1365 ++++++++++++++++- src/game/logic/map/terrainlogic.h | 47 +- .../logic/object/behavior/bridgebehavior.cpp | 34 + .../logic/object/behavior/bridgebehavior.h | 36 + .../object/behavior/bridgetowerbehavior.cpp | 34 + .../object/behavior/bridgetowerbehavior.h | 33 + src/game/logic/object/ghostobject.cpp | 19 + src/game/logic/object/ghostobject.h | 50 + src/game/logic/object/object.cpp | 2 + src/game/logic/object/object.h | 2 + src/game/logic/object/weapon.cpp | 2 - src/hooker/setupglobals_zh.cpp | 5 + src/hooker/setuphooks_zh.cpp | 34 + 22 files changed, 1703 insertions(+), 24 deletions(-) create mode 100644 src/game/logic/object/behavior/bridgebehavior.cpp create mode 100644 src/game/logic/object/behavior/bridgebehavior.h create mode 100644 src/game/logic/object/behavior/bridgetowerbehavior.cpp create mode 100644 src/game/logic/object/behavior/bridgetowerbehavior.h create mode 100644 src/game/logic/object/ghostobject.cpp create mode 100644 src/game/logic/object/ghostobject.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61ced2bcc..e28da6894 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -276,6 +276,7 @@ set(GAMEENGINE_SRC game/logic/object/armortemplateset.cpp game/logic/object/experiencetracker.cpp game/logic/object/firingtracker.cpp + game/logic/object/ghostobject.cpp game/logic/object/locomotor.cpp game/logic/object/object.cpp game/logic/object/objectcreationlist.cpp @@ -284,6 +285,8 @@ set(GAMEENGINE_SRC game/logic/object/weaponset.cpp game/logic/object/weapontemplateset.cpp game/logic/object/behavior/autohealbehavior.cpp + game/logic/object/behavior/bridgebehavior.cpp + game/logic/object/behavior/bridgetowerbehavior.cpp game/logic/object/behavior/overchargebehavior.cpp game/logic/object/behavior/rebuildholebehavior.cpp game/logic/object/collide/collidemodule.cpp diff --git a/src/game/client/terrainroads.h b/src/game/client/terrainroads.h index 2f66f5a92..5fe791ec7 100644 --- a/src/game/client/terrainroads.h +++ b/src/game/client/terrainroads.h @@ -62,6 +62,7 @@ class TerrainRoadType : public MemoryPoolObject Utf8String Get_Bridge_Model_Name_Really_Damaged() { return m_bridgeModelNameReallyDamaged; } Utf8String Get_Texture_Broken() { return m_textureBroken; } Utf8String Get_Bridge_Model_Name_Broken() { return m_bridgeModelNameBroken; } + Utf8String Get_Tower_Object_Name(BridgeTowerType type) { return m_towerObjectName[type]; } void Set_Name(Utf8String name) { m_name = name; } void Set_Damaged_OCL(int dmg, int effect, Utf8String name) { m_damagedTransitionOCL[dmg][effect] = name; } @@ -137,4 +138,4 @@ class TerrainRoadCollection : public SubsystemInterface extern TerrainRoadCollection *&g_theTerrainRoads; #else extern TerrainRoadCollection *g_theTerrainRoads; -#endif \ No newline at end of file +#endif diff --git a/src/game/common/mapobject.cpp b/src/game/common/mapobject.cpp index 65763afaf..82762ad66 100644 --- a/src/game/common/mapobject.cpp +++ b/src/game/common/mapobject.cpp @@ -18,7 +18,6 @@ #include "rendobj.h" #include "sideslist.h" #include "staticnamekey.h" -#include "terrainroads.h" #include "thingtemplate.h" #include #ifndef GAME_DLL @@ -260,9 +259,9 @@ void MapObject::Set_Name(Utf8String name) m_objectName = name; } -int MapObject::Get_Waypoint_ID() +WaypointID MapObject::Get_Waypoint_ID() { - return Get_Properties()->Get_Int(g_waypointIDKey); + return static_cast(Get_Properties()->Get_Int(g_waypointIDKey)); } Utf8String MapObject::Get_Waypoint_Name() @@ -270,7 +269,7 @@ Utf8String MapObject::Get_Waypoint_Name() return Get_Properties()->Get_AsciiString(g_waypointNameKey); } -void MapObject::Set_Waypoint_ID(int i) +void MapObject::Set_Waypoint_ID(WaypointID i) { Get_Properties()->Set_Int(g_waypointIDKey, i); } diff --git a/src/game/common/mapobject.h b/src/game/common/mapobject.h index 0621c1907..0007d07ba 100644 --- a/src/game/common/mapobject.h +++ b/src/game/common/mapobject.h @@ -16,6 +16,7 @@ #include "asciistring.h" #include "coord.h" #include "dict.h" +#include "terrainlogic.h" #include "terrainroads.h" class ThingTemplate; @@ -89,9 +90,9 @@ class MapObject : public MemoryPoolObject static void Fast_Assign_All_Unique_IDs(); void Set_Thing_Template(const ThingTemplate *thing); void Set_Name(Utf8String name); - int Get_Waypoint_ID(); + WaypointID Get_Waypoint_ID(); Utf8String Get_Waypoint_Name(); - void Set_Waypoint_ID(int i); + void Set_Waypoint_ID(WaypointID i); void Set_Waypoint_Name(Utf8String n); static int Count_Map_Objects_With_Owner(const Utf8String &n); const ThingTemplate *Get_Thing_Template(); diff --git a/src/game/common/staticnamekey.cpp b/src/game/common/staticnamekey.cpp index 0de8cd827..cccf2b3a2 100644 --- a/src/game/common/staticnamekey.cpp +++ b/src/game/common/staticnamekey.cpp @@ -127,6 +127,10 @@ StaticNameKey g_objectSoundAmbientVolume("objectSoundAmbientVolume"); StaticNameKey g_objectSoundAmbientMinRange("objectSoundAmbientMinRange"); StaticNameKey g_objectSoundAmbientMaxRange("objectSoundAmbientMaxRange"); StaticNameKey g_objectSoundAmbientPriority("objectSoundAmbientPriority"); +StaticNameKey g_waypointPathLabel1("waypointPathLabel1"); +StaticNameKey g_waypointPathLabel2("waypointPathLabel2"); +StaticNameKey g_waypointPathLabel3("waypointPathLabel3"); +StaticNameKey g_waypointPathBiDirectional("waypointPathBiDirectional"); NameKeyType StaticNameKey::Key() { diff --git a/src/game/common/staticnamekey.h b/src/game/common/staticnamekey.h index 8407d904c..9bcebca02 100644 --- a/src/game/common/staticnamekey.h +++ b/src/game/common/staticnamekey.h @@ -170,3 +170,7 @@ extern StaticNameKey g_objectSoundAmbientVolume; extern StaticNameKey g_objectSoundAmbientMinRange; extern StaticNameKey g_objectSoundAmbientMaxRange; extern StaticNameKey g_objectSoundAmbientPriority; +extern StaticNameKey g_waypointPathLabel1; +extern StaticNameKey g_waypointPathLabel2; +extern StaticNameKey g_waypointPathLabel3; +extern StaticNameKey g_waypointPathBiDirectional; diff --git a/src/game/common/system/geometry.h b/src/game/common/system/geometry.h index 7da8213e7..243ac5292 100644 --- a/src/game/common/system/geometry.h +++ b/src/game/common/system/geometry.h @@ -65,6 +65,7 @@ class GeometryInfo : public SnapShot float Get_Bounding_Circle_Radius() const { return m_boundingCircleRadius; } float Get_Bounding_Sphere_Radius() const { return m_boundingSphereRadius; } GeometryType Get_Type() const { return m_type; } + bool Is_Small() const { return m_isSmall; } private: GeometryType m_type; diff --git a/src/game/logic/ai/aipathfind.cpp b/src/game/logic/ai/aipathfind.cpp index c91fe36d9..1b5712310 100644 --- a/src/game/logic/ai/aipathfind.cpp +++ b/src/game/logic/ai/aipathfind.cpp @@ -280,3 +280,35 @@ void Pathfinder::Remove_Pos(Object *obj) Call_Method(PICK_ADDRESS(0x0056DA30, 0x0089E389), this, obj); #endif } + +void Pathfinder::Change_Bridge_State(PathfindLayerEnum layer, bool b) +{ +#ifdef GAME_DLL + Call_Method(PICK_ADDRESS(0x0056C970, 0x0089D713), this, layer, b); +#endif +} + +PathfindLayerEnum Pathfinder::Add_Bridge(Bridge *bridge) +{ +#ifdef GAME_DLL + return Call_Method(PICK_ADDRESS(0x0055F580, 0x008908BE), this, bridge); +#else + return LAYER_INVALID; +#endif +} + +bool Pathfinder::Is_Point_On_Wall(const Coord3D *point) +{ +#ifdef GAME_DLL + return Call_Method(PICK_ADDRESS(0x0055F450, 0x00890845), this, point); +#else + return false; +#endif +} + +void Pathfinder::Force_Map_Recalculation() +{ +#ifdef GAME_DLL + Call_Method(PICK_ADDRESS(0x00560E90, 0x00892519), this); +#endif +} diff --git a/src/game/logic/ai/aipathfind.h b/src/game/logic/ai/aipathfind.h index 9f47ebed9..59b7ce280 100644 --- a/src/game/logic/ai/aipathfind.h +++ b/src/game/logic/ai/aipathfind.h @@ -287,9 +287,14 @@ class Pathfinder : public PathfindServicesInterface, public SnapShot void Classify_Object_Footprint(Object *obj, bool insert); void Update_Pos(Object *obj, const Coord3D *pos); void Remove_Pos(Object *obj); + void Change_Bridge_State(PathfindLayerEnum layer, bool b); + PathfindLayerEnum Add_Bridge(Bridge *bridge); + bool Is_Point_On_Wall(const Coord3D *point); + void Force_Map_Recalculation(); void Remove_Object_From_Pathfind_Map(Object *obj) { Classify_Object_Footprint(obj, false); } void Add_Object_To_Pathfind_Map(Object *obj) { Classify_Object_Footprint(obj, true); } + float Get_Wall_Height() const { return m_wallHeight; } private: PathfindCell *m_mapPointer; // not 100% confirmed diff --git a/src/game/logic/map/terrainlogic.cpp b/src/game/logic/map/terrainlogic.cpp index 60f90110c..b7abd5ab6 100644 --- a/src/game/logic/map/terrainlogic.cpp +++ b/src/game/logic/map/terrainlogic.cpp @@ -13,10 +13,28 @@ * LICENSE */ #include "terrainlogic.h" +#include "ai.h" +#include "aipathfind.h" +#include "bodymodule.h" +#include "bridgebehavior.h" +#include "bridgetowerbehavior.h" +#include "damage.h" +#include "gamelogic.h" +#include "ghostobject.h" +#include "mapobject.h" #include "object.h" +#include "plane.h" +#include "polygontrigger.h" +#include "radar.h" +#include "staticnamekey.h" +#include "terrainroads.h" +#include "thingfactory.h" +#include "tri.h" +#include "view.h" #ifndef GAME_DLL TerrainLogic *g_theTerrainLogic = nullptr; +WaterHandle TerrainLogic::m_gridWaterHandle; #endif BridgeInfo::BridgeInfo() : @@ -34,14 +52,1205 @@ BridgeInfo::BridgeInfo() : } } +Waypoint::Waypoint(WaypointID id, + Utf8String name, + const Coord3D *loc, + Utf8String label1, + Utf8String label2, + Utf8String label3, + bool bidirectional) : + m_id(id), + m_name(name), + m_location(*loc), + m_next(nullptr), + m_numLinks(0), + m_pathLabel1(label1), + m_pathLabel2(label2), + m_pathLabel3(label3), + m_pathIsBiDirectional(bidirectional) +{ + for (int i = 0; i < MAX_LINKS; i++) { + m_links[i] = nullptr; + } +} + +Waypoint::~Waypoint() {} + +Object *Bridge::Create_Tower( + Coord3D *world_pos, BridgeTowerType tower_type, const ThingTemplate *tower_template, Object *bridge) +{ + if (tower_template != nullptr && bridge != nullptr) { + Object *tower = g_theThingFactory->New_Object(tower_template, bridge->Get_Team(), OBJECT_STATUS_MASK_NONE); + + switch (tower_type) { + case BRIDGE_TOWER_FROM_LEFT: + case BRIDGE_TOWER_FROM_RIGHT: { + float angle = bridge->Get_Orientation() + GAMEMATH_PI; + tower->Set_Position(world_pos); + tower->Set_Orientation(angle); + BridgeBehaviorInterface *bridge_behavior = BridgeBehavior::Get_Bridge_Behavior_Interface_From_Object(bridge); + captainslog_dbgassert( + bridge_behavior != nullptr, "Bridge::Create_Tower - no 'BridgeBehaviorInterface' found"); + + if (bridge_behavior != nullptr) { + bridge_behavior->Set_Tower(tower_type, tower); + } + + BridgeTowerBehaviorInterface *bridge_tower_behavior = + BridgeTowerBehavior::Get_Bridge_Tower_Behavior_Interface_From_Object(tower); + captainslog_dbgassert( + bridge_tower_behavior != nullptr, "Bridge::Create_Tower - no 'BridgeTowerBehaviorInterface' found"); + + if (bridge_tower_behavior != nullptr) { + bridge_tower_behavior->Set_Bridge(bridge); + bridge_tower_behavior->Set_Tower_Type(tower_type); + } + + if (bridge->Get_Body_Module()->Is_Indestructible()) { + tower->Get_Body_Module()->Set_Indestructible(true); + } + + return tower; + } + case BRIDGE_TOWER_TO_LEFT: + case BRIDGE_TOWER_TO_RIGHT: { + float angle = bridge->Get_Orientation(); + tower->Set_Position(world_pos); + tower->Set_Orientation(angle); + BridgeBehaviorInterface *bridge_behavior = BridgeBehavior::Get_Bridge_Behavior_Interface_From_Object(bridge); + captainslog_dbgassert( + bridge_behavior != nullptr, "Bridge::Create_Tower - no 'BridgeBehaviorInterface' found"); + + if (bridge_behavior != nullptr) { + bridge_behavior->Set_Tower(tower_type, tower); + } + + BridgeTowerBehaviorInterface *bridge_tower_behavior = + BridgeTowerBehavior::Get_Bridge_Tower_Behavior_Interface_From_Object(tower); + captainslog_dbgassert( + bridge_tower_behavior != nullptr, "Bridge::Create_Tower - no 'BridgeTowerBehaviorInterface' found"); + + if (bridge_tower_behavior != nullptr) { + bridge_tower_behavior->Set_Bridge(bridge); + bridge_tower_behavior->Set_Tower_Type(tower_type); + } + + if (bridge->Get_Body_Module()->Is_Indestructible()) { + tower->Get_Body_Module()->Set_Indestructible(true); + } + + return tower; + } + default: { + captainslog_dbgassert(false, "Bridge::Create_Tower - Unknown bridge tower type '%d'", tower_type); + return nullptr; + } + } + } else { + captainslog_dbgassert(false, "Bridge::Create_Tower(): Invalid params"); + return nullptr; + } +} + +Bridge::Bridge(BridgeInfo &info, Dict *props, Utf8String bridge_template_name) : m_bridgeInfo(info) +{ + m_templateName = bridge_template_name; + m_bounds.lo.x = m_bridgeInfo.from_left.x; + m_bounds.lo.y = m_bridgeInfo.from_left.y; + m_bounds.hi = m_bounds.lo; + + if (m_bounds.lo.x > m_bridgeInfo.from_right.x) { + m_bounds.lo.x = m_bridgeInfo.from_right.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.from_right.y) { + m_bounds.lo.y = m_bridgeInfo.from_right.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.from_right.x) { + m_bounds.hi.x = m_bridgeInfo.from_right.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.from_right.y) { + m_bounds.hi.y = m_bridgeInfo.from_right.y; + } + + if (m_bounds.lo.x > m_bridgeInfo.to_left.x) { + m_bounds.lo.x = m_bridgeInfo.to_left.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.to_left.y) { + m_bounds.lo.y = m_bridgeInfo.to_left.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.to_left.x) { + m_bounds.hi.x = m_bridgeInfo.to_left.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.to_left.y) { + m_bounds.hi.y = m_bridgeInfo.to_left.y; + } + + if (m_bounds.lo.x > m_bridgeInfo.to_right.x) { + m_bounds.lo.x = m_bridgeInfo.to_right.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.to_right.y) { + m_bounds.lo.y = m_bridgeInfo.to_right.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.to_right.x) { + m_bounds.hi.x = m_bridgeInfo.to_right.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.to_right.y) { + m_bounds.hi.y = m_bridgeInfo.to_right.y; + } + + m_bridgeInfo.cur_damage_state = BODY_PRISTINE; + static ThingTemplate *genericBridgeTemplate = g_theThingFactory->Find_Template("GenericBridge", true); + + if (genericBridgeTemplate != nullptr) { + Object *bridge = g_theThingFactory->New_Object(genericBridgeTemplate, nullptr, OBJECT_STATUS_MASK_NONE); + Coord3D pos = (m_bridgeInfo.from_left + m_bridgeInfo.to_right) / 2.0f; + bridge->Set_Position(&pos); + m_bridgeInfo.bridge_object_id = bridge->Get_ID(); + bridge->Update_Obj_Values_From_Map_Properties(props); + Coord2D c; + c.x = m_bridgeInfo.to_left.x - m_bridgeInfo.from_left.x; + c.y = m_bridgeInfo.to_left.y - m_bridgeInfo.from_left.y; + bridge->Set_Orientation(c.To_Angle()); + + if (g_theTerrainRoads->Find_Bridge(bridge_template_name)) { + m_next = nullptr; + } else { + captainslog_debug("*** Bridge Template Not Found '%s'.", bridge_template_name.Str()); + } + } else { + captainslog_debug("*** GenericBridge template not found."); + } +} + +Bridge::Bridge(Object *obj) +{ + m_templateName = obj->Get_Template()->Get_Name(); + + if (obj->Get_Geometry_Info().Get_Type() != GEOMETRY_BOX) { + captainslog_debug("Bridges need to be rectangles."); + } + + const Coord3D *pos = obj->Get_Position(); + float orientation = obj->Get_Orientation(); + float major_radius = obj->Get_Geometry_Info().Get_Major_Radius(); + float minor_radius = obj->Get_Geometry_Info().Get_Minor_Radius(); + m_bridgeInfo.bridge_width = 2.0f * minor_radius; + float cos = GameMath::Cos(orientation); + float sin = GameMath::Sin(orientation); + + m_bridgeInfo.from_left.Set( + pos->x - major_radius * cos - minor_radius * sin, minor_radius * cos + pos->y - major_radius * sin, pos->z); + m_bridgeInfo.to_left.Set( + major_radius * cos + pos->x - minor_radius * sin, minor_radius * cos + pos->y + major_radius * sin, pos->z); + m_bridgeInfo.from_right.Set( + pos->x - major_radius * cos + minor_radius * sin, pos->y - minor_radius * cos - major_radius * sin, pos->z); + m_bridgeInfo.to_right.Set( + major_radius * cos + pos->x + minor_radius * sin, pos->y - minor_radius * cos + major_radius * sin, pos->z); + + m_bridgeInfo.from = (m_bridgeInfo.from_left + m_bridgeInfo.from_right) / 2.0f; + m_bridgeInfo.to = (m_bridgeInfo.to_left + m_bridgeInfo.to_right) / 2.0f; + + m_bounds.lo.x = m_bridgeInfo.from_left.x; + m_bounds.lo.y = m_bridgeInfo.from_left.y; + m_bounds.hi = m_bounds.lo; + + if (m_bounds.lo.x > m_bridgeInfo.from_right.x) { + m_bounds.lo.x = m_bridgeInfo.from_right.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.from_right.y) { + m_bounds.lo.y = m_bridgeInfo.from_right.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.from_right.x) { + m_bounds.hi.x = m_bridgeInfo.from_right.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.from_right.y) { + m_bounds.hi.y = m_bridgeInfo.from_right.y; + } + + if (m_bounds.lo.x > m_bridgeInfo.to_left.x) { + m_bounds.lo.x = m_bridgeInfo.to_left.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.to_left.y) { + m_bounds.lo.y = m_bridgeInfo.to_left.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.to_left.x) { + m_bounds.hi.x = m_bridgeInfo.to_left.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.to_left.y) { + m_bounds.hi.y = m_bridgeInfo.to_left.y; + } + + if (m_bounds.lo.x > m_bridgeInfo.to_right.x) { + m_bounds.lo.x = m_bridgeInfo.to_right.x; + } + + if (m_bounds.lo.y > m_bridgeInfo.to_right.y) { + m_bounds.lo.y = m_bridgeInfo.to_right.y; + } + + if (m_bounds.hi.x < m_bridgeInfo.to_right.x) { + m_bounds.hi.x = m_bridgeInfo.to_right.x; + } + + if (m_bounds.hi.y < m_bridgeInfo.to_right.y) { + m_bounds.hi.y = m_bridgeInfo.to_right.y; + } + + m_bridgeInfo.cur_damage_state = BODY_PRISTINE; + m_bridgeInfo.bridge_object_id = obj->Get_ID(); + Utf8String bridge_template_name = obj->Get_Template()->Get_Name(); + TerrainRoadType *bridge = g_theTerrainRoads->Find_Bridge(bridge_template_name); + + if (bridge != nullptr) { + Coord2D c; + c.x = m_bridgeInfo.to_left.x - m_bridgeInfo.to_right.x; + c.y = m_bridgeInfo.to_left.y - m_bridgeInfo.to_right.y; + c.Normalize(); + Coord3D coords[BRIDGE_MAX_TOWERS]; + coords[0] = m_bridgeInfo.from_left; + coords[1] = m_bridgeInfo.from_right; + coords[2] = m_bridgeInfo.to_left; + coords[3] = m_bridgeInfo.to_right; + float radius = 5.0f; + + for (int i = 0; i < BRIDGE_MAX_TOWERS; i++) { + ThingTemplate *tower_template = + g_theThingFactory->Find_Template(bridge->Get_Tower_Object_Name(static_cast(i)), true); + + if (tower_template != nullptr) { + radius = tower_template->Get_Template_Geometry_Info().Get_Major_Radius(); + } + + Coord3D world_pos = coords[i]; + + switch (i) { + case BRIDGE_TOWER_FROM_LEFT: + case BRIDGE_TOWER_TO_LEFT: + world_pos.x = c.x * radius + world_pos.x; + world_pos.y = c.y * radius + world_pos.y; + break; + case BRIDGE_TOWER_FROM_RIGHT: + case BRIDGE_TOWER_TO_RIGHT: + world_pos.x = world_pos.x - c.x * radius; + world_pos.y = world_pos.y - c.y * radius; + break; + default: + break; + } + + Object *tower = Create_Tower(&world_pos, static_cast(i), tower_template, obj); + + if (tower != nullptr) { + m_bridgeInfo.tower_object_id[i] = tower->Get_ID(); + } + + m_next = nullptr; + } + } else { + captainslog_debug("*** Bridge Template Not Found '%s'.", bridge_template_name.Str()); + } +} + +Bridge::~Bridge() {} + +bool Bridge::Is_Point_On_Bridge(const Coord3D *loc) +{ + if (loc->x < m_bounds.lo.x) { + return false; + } + + if (loc->x > m_bounds.hi.x) { + return false; + } + + if (loc->y < m_bounds.lo.y) { + return false; + } + + if (loc->y > m_bounds.hi.y) { + return false; + } + + Vector3 loc_vec(loc->x, loc->y, loc->z); + Vector3 from_left_vec(m_bridgeInfo.from_left.x, m_bridgeInfo.from_left.y, m_bridgeInfo.from_left.z); + Vector3 from_right_vec(m_bridgeInfo.from_right.x, m_bridgeInfo.from_right.y, m_bridgeInfo.from_right.z); + Vector3 to_left_vec(m_bridgeInfo.to_left.x, m_bridgeInfo.to_left.y, m_bridgeInfo.to_left.z); + Vector3 to_right_vec(m_bridgeInfo.to_right.x, m_bridgeInfo.to_right.y, m_bridgeInfo.to_right.z); + unsigned char flags; + return Point_In_Triangle_2D(from_left_vec, from_right_vec, to_left_vec, loc_vec, 0, 1, flags) + || Point_In_Triangle_2D(from_right_vec, to_left_vec, to_right_vec, loc_vec, 0, 1, flags); +} + +bool Line_In_Region(const Coord2D *p1, const Coord2D *p2, const Region2D *clip_region) +{ + float c_l_x = clip_region->lo.x; + float c_h_x = clip_region->hi.x; + float c_l_y = clip_region->lo.y; + float c_h_y = clip_region->hi.y; + float p1_x = p1->x; + float p1_y = p1->y; + float p2_x = p2->x; + float p2_y = p2->y; + int clipCode1 = 0; + + if (p1->x >= c_l_x) { + if (p1_x > c_h_x) { + clipCode1 = 2; + } + } else { + clipCode1 = 1; + } + + if (p1_y >= c_l_y) { + if (p1_y > c_h_y) { + clipCode1 |= 4u; + } + } else { + clipCode1 |= 8u; + } + + int clipCode2 = 0; + + if (p2_x >= c_l_x) { + if (p2_x > c_h_x) { + clipCode2 = 2; + } + } else { + clipCode2 = 1; + } + + if (p2_y >= c_l_y) { + if (p2_y > c_h_y) { + clipCode2 |= 4u; + } + } else { + clipCode2 |= 8u; + } + + if ((clipCode2 | clipCode1) == 0) { + return true; + } + + if ((clipCode2 & clipCode1) != 0) { + return false; + } + + if (clipCode1) { + if ((clipCode1 & 8) != 0) { + if (p2_y - p1_y == 0.0f) { + return false; + } + + p1_x = (p2_x - p1_x) * (c_l_y - p1_y) / (p2_y - p1_y) + p1_x; + p1_y = c_l_y; + } else if ((clipCode1 & 4) != 0) { + if (p2_y - p1_y == 0.0f) { + return false; + } + + p1_x = (p2_x - p1_x) * (c_h_y - p1_y) / (p2_y - p1_y) + p1_x; + p1_y = c_h_y; + } + + if (p1_x <= c_h_x) { + if (p1_x < c_l_x) { + if (p2_x - p1_x == 0.0f) { + return false; + } + + p1_y = (p2_y - p1_y) * (c_l_x - p1_x) / (p2_x - p1_x) + p1_y; + p1_x = c_l_x; + } + } else { + if (p2_x - p1_x == 0.0f) { + return false; + } + + p1_y = (p2_y - p1_y) * (c_h_x - p1_x) / (p2_x - p1_x) + p1_y; + p1_x = c_h_x; + } + } + if (clipCode2) { + if ((clipCode2 & 8) != 0) { + if (p2_y - p1_y == 0.0f) { + return false; + } + + p2_x = (p2_x - p1_x) * (c_l_y - p2_y) / (p2_y - p1_y) + p2_x; + p2_y = c_l_y; + } else if ((clipCode2 & 4) != 0) { + if (p2_y - p1_y == 0.0f) { + return false; + } + + p2_x = (p2_x - p1_x) * (c_h_y - p2_y) / (p2_y - p1_y) + p2_x; + p2_y = c_h_y; + } + if (p2_x <= c_h_x) { + if (p2_x < c_l_x) { + if (p2_x - p1_x == 0.0f) { + return false; + } + + p2_y = (p2_y - p1_y) * (c_l_x - p2_x) / (p2_x - p1_x) + p2_y; + p2_x = c_l_x; + } + } else { + if (p2_x - p1_x == 0.0f) { + return 0; + } + + p2_y = (p2_y - p1_y) * (c_h_x - p2_x) / (p2_x - p1_x) + p2_y; + p2_x = c_h_x; + } + } + + return p1_x >= c_l_x && p1_x <= c_h_x && p1_y >= c_l_y && p1_y <= c_h_y && p2_x >= c_l_x && p2_x <= c_h_x + && p2_y >= c_l_y && p2_y <= c_h_y; +} + +bool Bridge::Is_Cell_On_End(const Region2D *cell) +{ + Coord3D c = m_bridgeInfo.from_right - m_bridgeInfo.from_left; + c.Normalize(); + c.x *= 10.0f; + c.y *= 10.0f; + + Coord3D from_left = m_bridgeInfo.from_left; + from_left.x += c.x; + from_left.y += c.y; + + Coord3D from_right = m_bridgeInfo.from_right; + from_right.x -= c.x; + from_right.y -= c.y; + + Coord3D to_left = m_bridgeInfo.to_left; + to_left.x += c.x; + to_left.y += c.y; + + Coord3D to_right = m_bridgeInfo.to_right; + to_right.x -= c.x; + to_right.y -= c.y; + + Coord2D p1; + p1.x = from_left.x; + p1.y = from_left.y; + + Coord2D p2; + p2.x = from_right.x; + p2.y = from_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + p1.x = to_left.x; + p1.y = to_left.y; + + p2.x = to_right.x; + p2.y = to_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + return false; +} + +bool Bridge::Is_Cell_On_Side(const Region2D *cell) +{ + Coord3D c = m_bridgeInfo.from_right - m_bridgeInfo.from_left; + c.Normalize(); + c.x *= 5.1f; + c.y *= 5.1f; + + Coord3D from_left = m_bridgeInfo.from_left; + from_left.x -= c.x; + from_left.y -= c.y; + + Coord3D from_right = m_bridgeInfo.from_right; + from_right.x += c.x; + from_right.y += c.y; + + Coord3D to_left = m_bridgeInfo.to_left; + to_left.x -= c.x; + to_left.y -= c.y; + + Coord3D to_right = m_bridgeInfo.to_right; + to_right.x += c.x; + to_right.y += c.y; + + Coord2D p1; + p1.x = from_left.x; + p1.y = from_left.y; + + Coord2D p2; + p2.x = to_left.x; + p2.y = to_left.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + p1.x = from_right.x; + p1.y = from_right.y; + + p2.x = to_right.x; + p2.y = to_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + from_left.x -= c.x; + from_left.y -= c.y; + + from_right.x += c.x; + from_right.y += c.y; + + to_left.x -= c.x; + to_left.y -= c.y; + + to_right.x += c.x; + to_right.y += c.y; + + p1.x = from_left.x; + p1.y = from_left.y; + + p2.x = to_left.x; + p2.y = to_left.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + p1.x = from_right.x; + p1.y = from_right.y; + + p2.x = to_right.x; + p2.y = to_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + return false; +} + +bool Bridge::Is_Cell_Entry_Point(const Region2D *cell) +{ + Coord3D c = m_bridgeInfo.from_right - m_bridgeInfo.from_left; + c.Normalize(); + c.x *= 10.0f; + c.y *= 10.0f; + + Coord3D c2 = m_bridgeInfo.to - m_bridgeInfo.from; + c2.Normalize(); + c2.x *= 5.0f; + c2.y *= 5.0f; + + Coord3D from_left = m_bridgeInfo.from_left; + from_left.x -= c2.x; + from_left.y -= c2.y; + from_left.x += c.x; + from_left.y += c.y; + + Coord3D from_right = m_bridgeInfo.from_right; + from_right.x -= c2.x; + from_right.y -= c2.y; + from_right.x -= c.x; + from_right.y -= c.y; + + Coord3D to_left = m_bridgeInfo.to_left; + to_left.x += c2.x; + to_left.y += c2.y; + to_left.x += c.x; + to_left.y += c.y; + + Coord3D to_right = m_bridgeInfo.to_right; + to_right.x += c2.x; + to_right.y += c2.y; + to_right.x -= c.x; + to_right.y -= c.y; + + Coord2D p1; + p1.x = from_left.x; + p1.y = from_left.y; + + Coord2D p2; + p2.x = from_right.x; + p2.y = from_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + p1.x = to_left.x; + p1.y = to_left.y; + + p2.x = to_right.x; + p2.y = to_right.y; + + if (Line_In_Region(&p1, &p2, cell)) { + return true; + } + + return false; +} + +Drawable *Bridge::Pick_Bridge(const Vector3 &from, const Vector3 &to, Vector3 *pos) +{ + Vector3 point1(m_bridgeInfo.from_left.x, m_bridgeInfo.from_left.y, m_bridgeInfo.from_left.z); + Vector3 point2(m_bridgeInfo.from_right.x, m_bridgeInfo.from_right.y, m_bridgeInfo.from_right.z); + Vector3 point3(m_bridgeInfo.to_left.x, m_bridgeInfo.to_left.y, m_bridgeInfo.to_left.z); + PlaneClass plane(point1, point2, point3); + + Vector3 p0; + Vector3 p1; + float k; + plane.Compute_Intersection(p0, p1, &k); + + Vector3 point = p0 + ((p1 - p0) * k); + Coord3D p; + p.x = point.X; + p.y = point.Y; + p.z = point.Z; + + if (Is_Point_On_Bridge(&p)) { + *pos = point; + Object *obj = g_theGameLogic->Find_Object_By_ID(m_bridgeInfo.bridge_object_id); + + if (obj != nullptr) { + return obj->Get_Drawable(); + } + } + + return nullptr; +} + +void Bridge::Update_Damage_State() +{ + m_bridgeInfo.is_destroyed = false; + + if (m_bridgeInfo.bridge_object_id != OBJECT_UNK) { + Object *bridge = g_theGameLogic->Find_Object_By_ID(m_bridgeInfo.bridge_object_id); + + if (bridge != nullptr) { + BodyDamageType damage_state = bridge->Get_Body_Module()->Get_Damage_State(); + BodyDamageType cur_damage_state = m_bridgeInfo.cur_damage_state; + + if (damage_state != cur_damage_state) { + m_bridgeInfo.cur_damage_state = damage_state; + + if (damage_state == BODY_RUBBLE) { + g_theAI->Get_Pathfinder()->Change_Bridge_State(m_layer, false); + m_bridgeInfo.is_destroyed = true; + + for (Object *obj = g_theGameLogic->Get_First_Object(); obj != nullptr; obj = obj->Get_Next_Object()) { + if (obj->Get_Layer() == m_layer) { + if (g_theTerrainLogic->Object_Interacts_With_Bridge_Layer(obj, obj->Get_Layer(), false)) { + DamageInfo info; + info.m_in.m_damageType = DAMAGE_FALLING; + info.m_in.m_deathType = DEATH_SPLATTED; + info.m_in.m_sourceID = obj->Get_ID(); + info.m_in.m_amount = 999999.0f; + obj->Attempt_Damage(&info); + } + } + } + } + + if (cur_damage_state == BODY_RUBBLE) { + BridgeBehaviorInterface *bridge_behavior = + BridgeBehavior::Get_Bridge_Behavior_Interface_From_Object(bridge); + + if (bridge_behavior == nullptr || !bridge_behavior->Is_Scaffold_Present()) { + g_theAI->Get_Pathfinder()->Change_Bridge_State(m_layer, true); + } + + m_bridgeInfo.is_destroyed = true; + } + } + } else { + m_bridgeInfo.bridge_object_id = OBJECT_UNK; + captainslog_dbgassert(false, "Bridge object disappeared - unexpected."); + } + } +} + +float Bridge::Get_Bridge_Height(const Coord3D *loc, Coord3D *n) +{ + Vector3 point1(m_bridgeInfo.from_left.x, m_bridgeInfo.from_left.y, m_bridgeInfo.from_left.z); + Vector3 point2(m_bridgeInfo.from_right.x, m_bridgeInfo.from_right.y, m_bridgeInfo.from_right.z); + Vector3 point3(m_bridgeInfo.to_left.x, m_bridgeInfo.to_left.y, m_bridgeInfo.to_left.z); + PlaneClass plane(point1, point2, point3); + + float z = 1000.0f; + Vector3 p0(loc->x, loc->y, 0.0f); + Vector3 p1(loc->x, loc->y, z); + float k; + plane.Compute_Intersection(p0, p1, &k); + + if (n != nullptr) { + n->x = plane.N.X; + n->y = plane.N.Y; + n->z = plane.N.Z; + } + + return k * z; +} + +void TerrainLogic::Init() {} + +void TerrainLogic::Reset() +{ + Delete_Waypoints(); + Delete_Bridges(); + PolygonTrigger::Delete_Triggers(); + m_numWaterToUpdate = 0; +} + +bool TerrainLogic::Is_Clear_Line_Of_Sight(const Coord3D &pos1, const Coord3D &pos2) const +{ + captainslog_dbgassert(false, "implement ME"); + return false; +} + +float TerrainLogic::Get_Ground_Height(float x, float y, Coord3D *n) const +{ + if (n != nullptr) { + n->Zero(); + } + + return 0.0f; +} + +float TerrainLogic::Get_Layer_Height(float x, float y, PathfindLayerEnum layer, Coord3D *n, bool b) const +{ + if (n != nullptr) { + n->Zero(); + } + + return 0.0f; +} + +bool TerrainLogic::Is_Cliff_Cell(float x, float y) const +{ + return false; +} + +bool TerrainLogic::Parse_Waypoint_Data_Chunk(DataChunkInput &file, DataChunkInfo *info, void *user_data) +{ + return static_cast(user_data)->Parse_Waypoint_Data(file, info, user_data); +} + +bool TerrainLogic::Parse_Waypoint_Data(DataChunkInput &file, DataChunkInfo *info, void *user_data) +{ + int count = file.Read_Int32(); + + for (int i = 0; i < count; i++) { + int id1 = file.Read_Int32(); + int id2 = file.Read_Int32(); + Add_Waypoint_Link(id1, id2); + } + + captainslog_dbgassert(file.At_End_Of_Chunk(), "Unexpected data left over."); + return true; +} + +void TerrainLogic::Add_Waypoint(MapObject *map_obj) +{ + Coord3D loc = *map_obj->Get_Location(); + loc.z = Get_Ground_Height(loc.x, loc.y, nullptr); + Utf8String label1; + Utf8String label2; + Utf8String label3; + bool exists; + label1 = map_obj->Get_Properties()->Get_AsciiString(g_waypointPathLabel1, &exists); + label2 = map_obj->Get_Properties()->Get_AsciiString(g_waypointPathLabel2, &exists); + label3 = map_obj->Get_Properties()->Get_AsciiString(g_waypointPathLabel3, &exists); + bool bidir = map_obj->Get_Properties()->Get_Bool(g_waypointPathBiDirectional, &exists); + captainslog_dbgassert(map_obj->Is_Waypoint(), "not a waypoint"); + Waypoint *waypoint = + new Waypoint(map_obj->Get_Waypoint_ID(), map_obj->Get_Waypoint_Name(), &loc, label1, label2, label3, bidir); + waypoint->Set_Next(m_waypointListHead); + m_waypointListHead = waypoint; +} + +void TerrainLogic::Add_Waypoint_Link(int id1, int id2) +{ + Waypoint *waypoint1 = nullptr; + Waypoint *waypoint2 = nullptr; + + for (Waypoint *waypoint = Get_First_Waypoint(); waypoint != nullptr; waypoint = waypoint->Get_Next()) { + if (waypoint->Get_ID() == id1) { + waypoint1 = waypoint; + } + + if (waypoint->Get_ID() == id2) { + waypoint2 = waypoint; + } + } + + if (waypoint1 != nullptr && waypoint2 != nullptr && waypoint1 != waypoint2) { + for (int i = 0; i < waypoint1->Get_Num_Links(); i++) { + if (waypoint1->Get_Link(i) == waypoint2) { + return; + } + } + + waypoint1->Set_Link(waypoint2); + + if (waypoint1->Is_Bi_Directional()) { + for (int i = 0; i < waypoint2->Get_Num_Links(); i++) { + if (waypoint2->Get_Link(i) == waypoint1) { + return; + } + } + + waypoint2->Set_Link(waypoint1); + } + } +} + +void TerrainLogic::Delete_Waypoints() +{ + Waypoint *next; + for (Waypoint *waypoint = Get_First_Waypoint(); waypoint != nullptr; waypoint = next) { + next = waypoint->Get_Next(); + waypoint->Set_Next(nullptr); + waypoint->Delete_Instance(); + } + + m_waypointListHead = nullptr; +} + +void TerrainLogic::Add_Bridge_To_Logic(BridgeInfo *info, Dict *props, Utf8String bridge_template_name) +{ + Bridge *bridge = new Bridge(*info, props, bridge_template_name); + bridge->Set_Next(m_bridgeListHead); + m_bridgeListHead = bridge; + bridge->Set_Layer(g_theAI->Get_Pathfinder()->Add_Bridge(bridge)); +} + +void TerrainLogic::Add_Landmark_Bridge_To_Logic(Object *obj) +{ + Bridge *bridge = new Bridge(obj); + bridge->Set_Next(m_bridgeListHead); + m_bridgeListHead = bridge; + bridge->Set_Layer(g_theAI->Get_Pathfinder()->Add_Bridge(bridge)); +} + +Waypoint *TerrainLogic::Get_Waypoint_By_Name(Utf8String name) +{ + for (Waypoint *waypoint = Get_First_Waypoint(); waypoint != nullptr; waypoint = waypoint->Get_Next()) { + if (name == waypoint->Get_Name()) { + return waypoint; + } + } + + return nullptr; +} + +Waypoint *TerrainLogic::Get_Waypoint_By_ID(WaypointID id) +{ + for (Waypoint *waypoint = Get_First_Waypoint(); waypoint != nullptr; waypoint = waypoint->Get_Next()) { + if (waypoint->Get_ID() == id) { + return waypoint; + } + } + + return nullptr; +} + +Waypoint *TerrainLogic::Get_Closest_Waypoint_On_Path(const Coord3D *pos, Utf8String label) +{ + float distance = 0.0f; + Waypoint *waypoint = nullptr; + + if (label.Is_Empty()) { + captainslog_debug("***Warning - asking for empty path label."); + return nullptr; + } else { + for (Waypoint *w = Get_First_Waypoint(); w != nullptr; w = w->Get_Next()) { + bool found = false; + + if (label.Compare_No_Case(w->Get_Path_Label_1()) == 0) { + found = true; + } + + if (label.Compare_No_Case(w->Get_Path_Label_2()) == 0) { + found = true; + } + + if (label.Compare_No_Case(w->Get_Path_Label_3()) == 0) { + found = true; + } + + if (found) { + Coord3D loc = *w->Get_Location(); + float dist = (loc.x - pos->x) * (loc.x - pos->x) + (loc.y - pos->y) * (loc.y - pos->y); + + if (waypoint != nullptr) { + if (dist < distance) { + waypoint = w; + distance = dist; + } + } else { + waypoint = w; + distance = dist; + } + } + } + + return waypoint; + } +} + +bool TerrainLogic::Is_Purpose_Of_Path(Waypoint *way, Utf8String label) +{ + if (!label.Is_Empty() && way != nullptr) { + if (way->Get_Path_Label_1() == label) { + return true; + } + + if (way->Get_Path_Label_2() == label) { + return true; + } + + if (way->Get_Path_Label_3() == label) { + return true; + } + + return false; + } else { + captainslog_debug("***Warning - asking for empth path label."); + return false; + } +} + +Bridge *TerrainLogic::Find_Bridge_At(const Coord3D *loc) const +{ + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge->Is_Point_On_Bridge(loc)) { + return bridge; + } + } + + return nullptr; +} + +Bridge *TerrainLogic::Find_Bridge_Layer_At(const Coord3D *loc, PathfindLayerEnum layer, bool b) const +{ + if (layer == LAYER_GROUND) { + return nullptr; + } + + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge->Get_Layer() == layer && (!b || bridge->Is_Point_On_Bridge(loc))) { + return bridge; + } + } + + return nullptr; +} + PathfindLayerEnum TerrainLogic::Get_Highest_Layer_For_Destination(const Coord3D *pos, bool b) { -#ifdef GAME_DLL - return Call_Method( - PICK_ADDRESS(0x0044B6B0, 0x00744B8E), this, pos, b); -#else - return LAYER_INVALID; -#endif + PathfindLayerEnum layer = LAYER_GROUND; + + float height = pos->z - Get_Ground_Height(pos->x, pos->y, nullptr); + + if (g_theAI->Get_Pathfinder()->Get_Wall_Height() / 2.0f < height) { + if (g_theAI->Get_Pathfinder()->Is_Point_On_Wall(pos)) { + float height_above_wall = pos->z - g_theAI->Get_Pathfinder()->Get_Wall_Height(); + + if (height_above_wall >= 0.0f) { + if (GameMath::Fabs(height) > GameMath::Fabs(height_above_wall)) { + layer = LAYER_WALLS; + height = height_above_wall; + } + } + } + } + + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if ((!b || bridge->Peek_Bridge_Info()->cur_damage_state != BODY_RUBBLE) && bridge->Is_Point_On_Bridge(pos)) { + float height_above_bridge = pos->z - bridge->Get_Bridge_Height(pos, nullptr); + + if (height_above_bridge >= 0.0f) { + if (GameMath::Fabs(height) > GameMath::Fabs(height_above_bridge)) { + layer = bridge->Get_Layer(); + height = height_above_bridge; + } + } + } + } + + return layer; +} + +bool TerrainLogic::Object_Interacts_With_Bridge_Layer(Object *obj, int layer, bool b) const +{ + if (layer == LAYER_GROUND) { + return false; + } + + if (layer == LAYER_WALLS) { + if (obj->Get_Layer() == LAYER_WALLS) { + return true; + } else { + return g_theAI->Get_Pathfinder()->Is_Point_On_Wall(obj->Get_Position()); + } + } else { + Bridge *bridge; + for (bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge == nullptr) { + return false; + } + + if (bridge->Get_Layer() == layer) { + break; + } + } + + bool found = false; + + if (bridge->Is_Point_On_Bridge(obj->Get_Position())) { + found = true; + } + + float radius = obj->Get_Geometry_Info().Get_Minor_Radius() + 5.0f; + Region2D cell; + cell.lo.x = obj->Get_Position()->x; + cell.lo.y = obj->Get_Position()->y; + cell.hi = cell.lo; + cell.lo.x -= radius; + cell.lo.y -= radius; + cell.hi.x += radius; + cell.hi.y += radius; + + if (bridge->Is_Cell_On_End(&cell)) { + found = true; + } + + if (found) { + float height = bridge->Get_Bridge_Height(obj->Get_Position(), nullptr); + return GameMath::Fabs(obj->Get_Position()->z - height) <= 10.0f + || (!b || bridge->Peek_Bridge_Info()->cur_damage_state != BODY_RUBBLE); + } else { + return false; + } + } +} + +bool TerrainLogic::Object_Interacts_With_Bridge_End(Object *obj, int layer) const +{ + if (layer == LAYER_GROUND) { + return false; + } + + Bridge *bridge; + for (bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge == nullptr) { + return false; + } + + if (bridge->Get_Layer() == layer) { + break; + } + } + + bool found = false; + + float radius = obj->Get_Geometry_Info().Get_Minor_Radius() + 5.0f; + Region2D cell; + cell.lo.x = obj->Get_Position()->x; + cell.lo.y = obj->Get_Position()->y; + cell.hi = cell.lo; + cell.lo.x = cell.lo.x - radius; + cell.lo.y = cell.lo.y - radius; + cell.hi.x = cell.hi.x + radius; + cell.hi.y = cell.hi.y + radius; + + if (bridge->Is_Cell_On_End(&cell)) { + found = true; + } + + if (found) { + float height = bridge->Get_Bridge_Height(obj->Get_Position(), nullptr); + return GameMath::Fabs(obj->Get_Position()->z - height) <= 10.0f; + } else { + return false; + } +} + +void TerrainLogic::Update_Bridge_Damage_States() +{ + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + bridge->Update_Damage_State(); + } + + m_bridgeDamageStatesChanged = true; +} + +bool TerrainLogic::Is_Bridge_Repaired(const Object *bridge) +{ + if (bridge == nullptr) { + return false; + } + + ObjectID id = bridge->Get_ID(); + + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge == nullptr) { + return false; + } + + const BridgeInfo *info = bridge->Peek_Bridge_Info(); + if (info->bridge_object_id == id) { + return info->is_destroyed && info->cur_damage_state != BODY_RUBBLE; + } + } + + return false; +} + +bool TerrainLogic::Is_Bridge_Broken(const Object *bridge) +{ + if (bridge == nullptr) { + return false; + } + + ObjectID id = bridge->Get_ID(); + + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + if (bridge == nullptr) { + return false; + } + + const BridgeInfo *info = bridge->Peek_Bridge_Info(); + if (info->bridge_object_id == id) { + return info->is_destroyed && info->cur_damage_state == BODY_RUBBLE; + } + } + + return false; } void TerrainLogic::Get_Bridge_Attack_Points(const Object *bridge, TBridgeAttackInfo *attack_info) @@ -64,3 +1273,147 @@ void TerrainLogic::Get_Bridge_Attack_Points(const Object *bridge, TBridgeAttackI attack_info->m_attackPoint1 = *bridge->Get_Position(); attack_info->m_attackPoint2 = *bridge->Get_Position(); } + +Drawable *TerrainLogic::Pick_Bridge(const Vector3 &from, const Vector3 &to, Vector3 *pos) +{ + Drawable *drawable = nullptr; + Vector3 loc(0.0f, 0.0f, 0.0f); + + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = bridge->Get_Next()) { + Vector3 v; + Drawable *d = bridge->Pick_Bridge(from, to, &v); + + if (drawable == nullptr) { + drawable = d; + loc = v; + } + } + + *pos = loc; + return drawable; +} + +void TerrainLogic::Delete_Bridges() +{ + Bridge *next; + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = next) { + next = bridge->Get_Next(); + bridge->Set_Next(nullptr); + bridge->Delete_Instance(); + } + + m_bridgeListHead = nullptr; +} + +void TerrainLogic::Delete_Bridge(Bridge *bridge) +{ + if (bridge != nullptr) { + if (m_bridgeListHead == bridge) { + m_bridgeListHead = bridge->Get_Next(); + } else { + for (Bridge *b = Get_First_Bridge(); b != nullptr; b = b->Get_Next()) { + if (b->Get_Next() == bridge) { + b->Set_Next(bridge->Get_Next()); + break; + } + } + } + + BridgeInfo info; + bridge->Get_Bridge_Info(&info); + g_theAI->Get_Pathfinder()->Change_Bridge_State(bridge->Get_Layer(), false); + Object *obj = g_theGameLogic->Find_Object_By_ID(info.bridge_object_id); + + if (obj != nullptr) { + g_theGameLogic->Destroy_Object(obj); + } + + bridge->Delete_Instance(); + } +} + +void TerrainLogic::Set_Active_Boundary(int new_active_boundary) +{ + if (new_active_boundary >= 0 && new_active_boundary < m_boundaries.size() && new_active_boundary != m_activeBoundary + && m_boundaries[new_active_boundary].x != 0 && m_boundaries[new_active_boundary].y != 0) { + ShroudStatusStoreRestore shroud; + g_thePartitionManager->Process_Entire_Pending_Undo_Shroud_Reveal_Queue(); + g_thePartitionManager->Store_Fogged_Cells(shroud, true); + m_activeBoundary = new_active_boundary; + g_theGhostObjectManager->Release_Partition_Data(); + + for (Object *obj = g_theGameLogic->Get_First_Object(); obj != nullptr; obj = obj->Get_Next_Object()) { + obj->Friend_Prepare_For_Map_Boundary_Adjust(); + } + + g_thePartitionManager->Store_Fogged_Cells(shroud, false); + g_thePartitionManager->Reset(); + g_thePartitionManager->Init(); + g_theRadar->New_Map(g_theTerrainLogic); + g_thePartitionManager->Restore_Fogged_Cells(shroud, false); + g_theGhostObjectManager->Set_Updating_Map_Boundary(true); + + for (Object *obj = g_theGameLogic->Get_First_Object(); obj != nullptr; obj = obj->Get_Next_Object()) { + obj->Friend_Notify_Of_New_Map_Boundary(); + } + + g_thePartitionManager->Restore_Fogged_Cells(shroud, true); + g_theGhostObjectManager->Restore_Partition_Data(); + g_theGhostObjectManager->Set_Updating_Map_Boundary(false); + g_theTacticalView->Force_Camera_Constraint_Recalc(); + } +} + +void TerrainLogic::CRC_Snapshot(Xfer *xfer) {} + +void TerrainLogic::Xfer_Snapshot(Xfer *xfer) +{ + unsigned char version = 2; + xfer->xferVersion(&version, 2); + int boundary = m_activeBoundary; + xfer->xferInt(&boundary); + + if (xfer->Get_Mode() == XFER_LOAD) { + Set_Active_Boundary(boundary); + } + + if (version >= 2) { + xfer->xferInt(&m_numWaterToUpdate); + + for (int i = 0; i < m_numWaterToUpdate; i++) { + if (xfer->Get_Mode() == XFER_SAVE) { + int id = m_waterToUpdate[i].water_table->m_polygon->Get_ID(); + xfer->xferInt(&id); + } else if (xfer->Get_Mode() == XFER_LOAD) { + int id; + xfer->xferInt(&id); + PolygonTrigger *trigger = PolygonTrigger::Get_Polygon_Trigger_By_ID(id); + captainslog_relassert(trigger != nullptr, + CODE_06, + "TerrainLogic::Xfer_Snapshot - Unable to find polygon trigger for water table with trigger ID '%d'", + id); + m_waterToUpdate[i].water_table = trigger->Get_Water_Handle(); + captainslog_relassert(m_waterToUpdate[i].water_table != nullptr, + CODE_06, + "TerrainLogic::Xfer_Snapshot - Polygon trigger to use for water handle has no water handle!"); + } + + xfer->xferReal(&m_waterToUpdate[i].change_per_frame); + xfer->xferReal(&m_waterToUpdate[i].target_height); + xfer->xferReal(&m_waterToUpdate[i].damage_amount); + xfer->xferReal(&m_waterToUpdate[i].current_height); + } + } +} + +void TerrainLogic::Load_Post_Process() +{ + Bridge *next; + for (Bridge *bridge = Get_First_Bridge(); bridge != nullptr; bridge = next) { + next = bridge->Get_Next(); + + if (g_theGameLogic->Find_Object_By_ID(bridge->Peek_Bridge_Info()->bridge_object_id) == nullptr) { + Delete_Bridge(bridge); + } + } +} diff --git a/src/game/logic/map/terrainlogic.h b/src/game/logic/map/terrainlogic.h index d2d448bfb..b404d89e4 100644 --- a/src/game/logic/map/terrainlogic.h +++ b/src/game/logic/map/terrainlogic.h @@ -89,6 +89,9 @@ class Bridge : public MemoryPoolObject Bridge *Get_Next() { return m_next; } void Set_Layer(PathfindLayerEnum layer) { m_layer = layer; } PathfindLayerEnum Get_Layer() { return m_layer; } + Region2D Get_Bounds() { return m_bounds; } + Utf8String Get_Name() { return m_templateName; } + void Set_Object_ID(ObjectID id) { m_bridgeInfo.bridge_object_id = id; } private: Bridge *m_next; @@ -115,10 +118,22 @@ class Waypoint : public MemoryPoolObject bool bidirectional); void Set_Next(Waypoint *waypoint) { m_next = waypoint; } - void Set_Link(int link, Waypoint *waypoint) { m_links[link] = waypoint; } + void Set_Link(Waypoint *waypoint) + { + if (m_numLinks < MAX_LINKS) { + m_links[m_numLinks++] = waypoint; + } + } Waypoint *Get_Next() const { return m_next; } int Get_Num_Links() const { return m_numLinks; } - Waypoint *Get_Link(int link) const { return m_links[link]; } + Waypoint *Get_Link(int link) const + { + if (link > MAX_LINKS) { + return nullptr; + } else { + return m_links[link]; + } + } Utf8String Get_Name() const { return m_name; } WaypointID Get_ID() const { return m_id; } const Coord3D *Get_Location() const { return &m_location; } @@ -154,6 +169,12 @@ class WaterHandle PolygonTrigger *m_polygon; }; +struct ShroudStatusStoreRestore +{ + std::vector status[MAX_PLAYER_COUNT]; + int width; +}; + class TerrainLogic : public SnapShot, public SubsystemInterface { public: @@ -172,13 +193,13 @@ class TerrainLogic : public SnapShot, public SubsystemInterface virtual void New_Map(bool b); virtual float Get_Ground_Height(float x, float y, Coord3D *n) const; virtual float Get_Layer_Height(float x, float y, PathfindLayerEnum layer, Coord3D *n, bool b) const; - virtual void Get_Extent(Region3D *extent) const; - virtual void Get_Extent_Including_Border(Region3D *extent) const; - virtual void Get_Maximum_Pathfind_Extent(Region3D *extent) const; + virtual void Get_Extent(Region3D *extent) const { captainslog_dbgassert(false, "not implemented"); } + virtual void Get_Extent_Including_Border(Region3D *extent) const { captainslog_dbgassert(false, "not implemented"); } + virtual void Get_Maximum_Pathfind_Extent(Region3D *extent) const { captainslog_dbgassert(false, "not implemented"); } virtual Coord3D Find_Closest_Edge_Point(const Coord3D *pos) const; virtual Coord3D Find_Farthest_Edge_Point(const Coord3D *pos) const; virtual bool Is_Clear_Line_Of_Sight(const Coord3D &pos1, const Coord3D &pos2) const; - virtual Utf8String Get_Source_Filename(); + virtual Utf8String Get_Source_Filename() { return m_filenameString; } virtual PathfindLayerEnum Align_On_Terrain(float angle, const Coord3D &pos, bool stick_to_ground, Matrix3D &mtx); virtual bool Is_Underwater(float x, float y, float *waterz, float *groundz); virtual bool Is_Cliff_Cell(float x, float y) const; @@ -188,13 +209,13 @@ class TerrainLogic : public SnapShot, public SubsystemInterface virtual void Set_Water_Height(const WaterHandle *water, float height, float damage_amount, bool force_pathfind_update); virtual void Change_Water_Height_Over_Time( const WaterHandle *water, float final_height, float transition_time_in_seconds, float damage_amount); - virtual Waypoint *Get_First_Waypoint(); + virtual Waypoint *Get_First_Waypoint() { return m_waypointListHead; } virtual Waypoint *Get_Waypoint_By_Name(Utf8String name); virtual Waypoint *Get_Waypoint_By_ID(WaypointID id); virtual Waypoint *Get_Closest_Waypoint_On_Path(const Coord3D *pos, Utf8String label); virtual bool Is_Purpose_Of_Path(Waypoint *way, Utf8String label); virtual PolygonTrigger *Get_Trigger_Area_By_Name(Utf8String name); - virtual Bridge *Get_First_Bridge() const; + virtual Bridge *Get_First_Bridge() const { return m_bridgeListHead; } virtual Bridge *Find_Bridge_At(const Coord3D *loc) const; virtual Bridge *Find_Bridge_Layer_At(const Coord3D *loc, PathfindLayerEnum layer, bool b) const; virtual bool Object_Interacts_With_Bridge_Layer(Object *obj, int layer, bool b) const; @@ -221,10 +242,16 @@ class TerrainLogic : public SnapShot, public SubsystemInterface PathfindLayerEnum Get_Layer_For_Destination(const Coord3D *pos); PathfindLayerEnum Get_Highest_Layer_For_Destination(const Coord3D *pos, bool b); + bool Have_Bridge_Damage_States_Changed() { return m_bridgeDamageStatesChanged; } + static bool Parse_Waypoint_Data_Chunk(DataChunkInput &file, DataChunkInfo *info, void *user_data); protected: +#ifdef GAME_DLL + static WaterHandle &m_gridWaterHandle; +#else static WaterHandle m_gridWaterHandle; +#endif enum { @@ -233,7 +260,7 @@ class TerrainLogic : public SnapShot, public SubsystemInterface struct DynamicWaterEntry { - void *water_table; + const WaterHandle *water_table; float change_per_frame; float target_height; float damage_amount; @@ -259,3 +286,5 @@ extern TerrainLogic *&g_theTerrainLogic; #else extern TerrainLogic *g_theTerrainLogic; #endif + +bool Line_In_Region(const Coord2D *p1, const Coord2D *p2, const Region2D *clip_region); diff --git a/src/game/logic/object/behavior/bridgebehavior.cpp b/src/game/logic/object/behavior/bridgebehavior.cpp new file mode 100644 index 000000000..266d940d6 --- /dev/null +++ b/src/game/logic/object/behavior/bridgebehavior.cpp @@ -0,0 +1,34 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Bridge Behavior + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "bridgebehavior.h" +#include "behaviormodule.h" +#include "object.h" + +BridgeBehaviorInterface *BridgeBehavior::Get_Bridge_Behavior_Interface_From_Object(Object *obj) +{ + if (obj == nullptr) { + return nullptr; + } + + for (BehaviorModule **module = obj->Get_All_Modules(); *module != nullptr; module++) { + BridgeBehaviorInterface *bridge = (*module)->Get_Bridge_Behavior_Interface(); + + if (bridge != nullptr) { + return bridge; + } + } + + return nullptr; +} diff --git a/src/game/logic/object/behavior/bridgebehavior.h b/src/game/logic/object/behavior/bridgebehavior.h new file mode 100644 index 000000000..5cad81264 --- /dev/null +++ b/src/game/logic/object/behavior/bridgebehavior.h @@ -0,0 +1,36 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Bridge Behavior + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once +#include "always.h" +#include "terrainroads.h" + +class Object; + +class BridgeBehaviorInterface +{ +public: + virtual void Set_Tower(BridgeTowerType type, Object *obj) = 0; + virtual ObjectID Get_Tower_ID(BridgeTowerType type) = 0; + virtual void Create_Scaffolding() = 0; + virtual void Remove_Scaffolding() = 0; + virtual bool Is_Scaffold_In_Motion() = 0; + virtual bool Is_Scaffold_Present() = 0; +}; + +class BridgeBehavior +{ +public: + static BridgeBehaviorInterface *Get_Bridge_Behavior_Interface_From_Object(Object *obj); +}; diff --git a/src/game/logic/object/behavior/bridgetowerbehavior.cpp b/src/game/logic/object/behavior/bridgetowerbehavior.cpp new file mode 100644 index 000000000..7ef1bf017 --- /dev/null +++ b/src/game/logic/object/behavior/bridgetowerbehavior.cpp @@ -0,0 +1,34 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Bridge Tower Behavior + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "bridgetowerbehavior.h" +#include "behaviormodule.h" +#include "object.h" + +BridgeTowerBehaviorInterface *BridgeTowerBehavior::Get_Bridge_Tower_Behavior_Interface_From_Object(Object *obj) +{ + if (obj == nullptr || !obj->Is_KindOf(KINDOF_BRIDGE_TOWER)) { + return nullptr; + } + + for (BehaviorModule **module = obj->Get_All_Modules(); *module != nullptr; module++) { + BridgeTowerBehaviorInterface *tower = (*module)->Get_Bridge_Tower_Behavior_Interface(); + + if (tower != nullptr) { + return tower; + } + } + + return nullptr; +} diff --git a/src/game/logic/object/behavior/bridgetowerbehavior.h b/src/game/logic/object/behavior/bridgetowerbehavior.h new file mode 100644 index 000000000..4a5dd9a90 --- /dev/null +++ b/src/game/logic/object/behavior/bridgetowerbehavior.h @@ -0,0 +1,33 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Bridge Tower Behavior + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once +#include "always.h" +#include "terrainroads.h" + +class Object; + +class BridgeTowerBehaviorInterface +{ +public: + virtual void Set_Bridge(Object *obj) = 0; + virtual ObjectID Get_Bridge_ID() = 0; + virtual void Set_Tower_Type(BridgeTowerType type) = 0; +}; + +class BridgeTowerBehavior +{ +public: + static BridgeTowerBehaviorInterface *Get_Bridge_Tower_Behavior_Interface_From_Object(Object *obj); +}; diff --git a/src/game/logic/object/ghostobject.cpp b/src/game/logic/object/ghostobject.cpp new file mode 100644 index 000000000..4809b181a --- /dev/null +++ b/src/game/logic/object/ghostobject.cpp @@ -0,0 +1,19 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Ghost Object + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#include "ghostobject.h" + +#ifndef GAME_DLL +GhostObjectManager *g_theGhostObjectManager = nullptr; +#endif diff --git a/src/game/logic/object/ghostobject.h b/src/game/logic/object/ghostobject.h new file mode 100644 index 000000000..49115b730 --- /dev/null +++ b/src/game/logic/object/ghostobject.h @@ -0,0 +1,50 @@ +/** + * @file + * + * @author Jonathan Wilson + * + * @brief Ghost Object + * + * @copyright Thyme 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 + * 2 of the License, or (at your option) any later version. + * A full copy of the GNU General Public License can be found in + * LICENSE + */ +#pragma once +#include "always.h" +#include "snapshot.h" + +class GhostObject; +class Object; +class PartitionData; + +class GhostObjectManager : public SnapShot +{ +public: + virtual void CRC_Snapshot(Xfer *xfer) override; + virtual void Xfer_Snapshot(Xfer *xfer) override; + virtual void Load_Post_Process() override; + virtual ~GhostObjectManager(); + virtual void Reset(); + virtual GhostObject *Add_Ghost_Object(Object *obj, PartitionData *data); + virtual void Remove_Ghost_Object(GhostObject *obj); + virtual void Set_Local_Player_Index(int index); + virtual void Update_Orphaned_Objects(int *unk, int unk2); + virtual void Release_Partition_Data(); + virtual void Restore_Partition_Data(); + + void Set_Updating_Map_Boundary(bool update) { m_isUpdatingMapBoundary = update; } + +private: + int m_localPlayerIndex; + bool m_isUpdatingMapBoundary; + bool m_isLoading; +}; + +#ifdef GAME_DLL +extern GhostObjectManager *&g_theGhostObjectManager; +#else +extern GhostObjectManager *g_theGhostObjectManager; +#endif diff --git a/src/game/logic/object/object.cpp b/src/game/logic/object/object.cpp index 484f9089b..e0272850e 100644 --- a/src/game/logic/object/object.cpp +++ b/src/game/logic/object/object.cpp @@ -68,6 +68,8 @@ #include "w3ddebugicons.h" #include "weaponset.h" +BitFlags OBJECT_STATUS_MASK_NONE; + template<> const char *BitFlags::s_bitNamesList[] = { "SPECIAL_INVALID", "SPECIAL_DAISY_CUTTER", diff --git a/src/game/logic/object/object.h b/src/game/logic/object/object.h index 19fab3c3a..8b69cf7cf 100644 --- a/src/game/logic/object/object.h +++ b/src/game/logic/object/object.h @@ -665,3 +665,5 @@ class Object : public Thing, public SnapShot bool m_singleUseCommand; bool m_receivingDifficultyBonus; }; + +extern BitFlags OBJECT_STATUS_MASK_NONE; diff --git a/src/game/logic/object/weapon.cpp b/src/game/logic/object/weapon.cpp index e0d3b86a4..358ac7ab4 100644 --- a/src/game/logic/object/weapon.cpp +++ b/src/game/logic/object/weapon.cpp @@ -40,8 +40,6 @@ #include "thingfactory.h" #include "weaponset.h" -BitFlags OBJECT_STATUS_MASK_NONE; - void Do_FX_Pos(FXList const *list, const Coord3D *primary, const Matrix3D *primary_mtx, diff --git a/src/hooker/setupglobals_zh.cpp b/src/hooker/setupglobals_zh.cpp index 969865ecd..9c5ec0862 100644 --- a/src/hooker/setupglobals_zh.cpp +++ b/src/hooker/setupglobals_zh.cpp @@ -534,6 +534,7 @@ AI *&g_theAI = Make_Global(PICK_ADDRESS(0x00A2BBF4, 0x00E27E24)); // terrainlogic.cpp #include "terrainlogic.h" TerrainLogic *&g_theTerrainLogic = Make_Global(PICK_ADDRESS(0x00A2B680, 0x00E23AE8)); +WaterHandle &m_gridWaterHandle = Make_Global(PICK_ADDRESS(0x00A2B67C, 0x00E23AE4)); // assetmgr.cpp #include "assetmgr.h" @@ -770,3 +771,7 @@ GameInfo *&g_theGameInfo = Make_Global(PICK_ADDRESS(0x00A2C2B8, 0x04 // eva.cpp class Eva; Eva *&g_theEva = Make_Global(PICK_ADDRESS(0x00A2C090, 0x04CA9C28)); + +// ghostobject.cpp +class GhostObjectManager; +GhostObjectManager *&g_theGhostObjectManager = Make_Global(PICK_ADDRESS(0x00A2C280, 0x04CA8CD8)); diff --git a/src/hooker/setuphooks_zh.cpp b/src/hooker/setuphooks_zh.cpp index 9183b6c77..44b7202b7 100644 --- a/src/hooker/setuphooks_zh.cpp +++ b/src/hooker/setuphooks_zh.cpp @@ -150,6 +150,7 @@ #include "targa.h" #include "team.h" #include "teamsinfo.h" +#include "terrainlogic.h" #include "terraintex.h" #include "texproject.h" #include "texture.h" @@ -2931,4 +2932,37 @@ void Setup_Hooks() Hook_Any(0x005757F0, AcademyStats::Record_Special_Power_Used); Hook_Any(0x00575810, AcademyStats::Record_Income); Hook_Any(0x00576B70, AcademyStats::Calculate_Academy_Advice); + + // terrainlogic.h + Hook_Any(0x0044A650, TerrainLogic::Add_Waypoint); + Hook_Any(0x0044AC20, TerrainLogic::Add_Bridge_To_Logic); + Hook_Any(0x0044AD70, TerrainLogic::Add_Landmark_Bridge_To_Logic); + Hook_Any(0x004485C0, Bridge::Is_Point_On_Bridge); + Hook_Any(0x00448A30, Line_In_Region); + Hook_Any(0x00448E50, Bridge::Is_Cell_On_End); + Hook_Any(0x00448FE0, Bridge::Is_Cell_On_Side); + Hook_Any(0x00449260, Bridge::Is_Cell_Entry_Point); + Hook_Any(0x0044BBC0, TerrainLogic::Pick_Bridge); + Hook_Any(0x0044B9C0, TerrainLogic::Update_Bridge_Damage_States); + Hook_Any(0x00449A80, Bridge::Get_Bridge_Height); + Hook_Any(0x0044B540, TerrainLogic::Find_Bridge_At); + Hook_Any(0x0044B580, TerrainLogic::Find_Bridge_Layer_At); + Hook_Any(0x0044BC40, TerrainLogic::Delete_Bridge); + Hook_Any(0x0044B9F0, TerrainLogic::Is_Bridge_Repaired); + Hook_Any(0x0044BA30, TerrainLogic::Is_Bridge_Broken); + Hook_Any(0x0044B7C0, TerrainLogic::Object_Interacts_With_Bridge_Layer); + Hook_Any(0x00744EF8, TerrainLogic::Object_Interacts_With_Bridge_End); + Hook_Any(0x0044B6B0, TerrainLogic::Get_Highest_Layer_For_Destination); + Hook_Any(0x0044A610, TerrainLogic::Parse_Waypoint_Data_Chunk); + Hook_Any(0x00449E30, TerrainLogic::Reset); + Hook_Any(0x0044A9D0, TerrainLogic::Get_Ground_Height); + Hook_Any(0x0044A9F0, TerrainLogic::Get_Layer_Height); + Hook_Any(0x0044AE20, TerrainLogic::Get_Waypoint_By_Name); + Hook_Any(0x0044AF60, TerrainLogic::Get_Waypoint_By_ID); + Hook_Any(0x0044AF80, TerrainLogic::Get_Closest_Waypoint_On_Path); + Hook_Any(0x0044B1A0, TerrainLogic::Is_Purpose_Of_Path); + Hook_Any(0x0044E070, TerrainLogic::Xfer_Snapshot); + Hook_Any(0x0044E1B0, TerrainLogic::Load_Post_Process); + Hook_Any(0x0044C9D0, TerrainLogic::Set_Active_Boundary); + Hook_Any(0x0044A920, TerrainLogic::Add_Waypoint_Link); }