#pragma once #include #include #include #include "KingSystem/Utils/BitField.h" namespace ksys::phys { enum class ContactLayerType { Entity, Sensor, Invalid, }; constexpr int NumContactLayerTypes = 2; SEAD_ENUM(ContactLayer, EntityObject,\ EntitySmallObject,\ EntityGroundObject,\ EntityPlayer,\ EntityNPC,\ EntityRagdoll,\ EntityWater,\ EntityAirWall,\ EntityGround,\ EntityGroundSmooth,\ EntityGroundRough,\ EntityRope,\ EntityTree,\ EntityNPC_NoHitPlayer,\ EntityHitOnlyWater,\ EntityWallForClimb,\ EntityHitOnlyGround,\ EntityQueryCustomReceiver,\ EntityForbidden18,\ EntityNoHit,\ EntityMeshVisualizer,\ EntityForbidden21,\ EntityForbidden22,\ EntityForbidden23,\ EntityForbidden24,\ EntityForbidden25,\ EntityForbidden26,\ EntityForbidden27,\ EntityForbidden28,\ EntityForbidden29,\ EntityForbidden30,\ EntityEnd,\ SensorObject,\ SensorSmallObject,\ SensorPlayer,\ SensorEnemy,\ SensorNPC,\ SensorHorse,\ SensorRope,\ SensorAttackPlayer,\ SensorAttackEnemy,\ SensorChemical,\ SensorTerror,\ SensorHitOnlyInDoor,\ SensorInDoor,\ SensorReserve13,\ SensorReserve14,\ SensorChemicalElement,\ SensorAttackCommon,\ SensorQueryOnly,\ SensorTree,\ SensorCamera,\ SensorMeshVisualizer,\ SensorNoHit,\ SensorReserve20,\ SensorCustomReceiver,\ SensorEnd) constexpr int MaxNumLayersPerType = 32; constexpr auto FirstEntity = ContactLayer::EntityObject; constexpr auto LastEntity = ContactLayer::EntityMeshVisualizer; constexpr auto FirstSensor = ContactLayer::SensorObject; constexpr auto LastSensor = ContactLayer::SensorCustomReceiver; // If the values of the two following constants are changed, most of the BitFields defined // in this file will need to be updated. constexpr int NumRegularEntityLayers = ContactLayer::EntityHitOnlyGround - FirstEntity + 1; constexpr int NumRegularSensorLayers = ContactLayer::SensorMeshVisualizer - FirstSensor + 1; static_assert(NumRegularEntityLayers == 17); static_assert(NumRegularSensorLayers == 21); constexpr bool isEntityGroundLayer(ContactLayer::ValueType layer) { return layer == ContactLayer::EntityGround || layer == ContactLayer::EntityGroundSmooth || layer == ContactLayer::EntityGroundRough; } SEAD_ENUM(Material, Undefined,\ Soil,\ Stone,\ Sand,\ Metal,\ WireNet,\ Grass,\ Wood,\ Water,\ Snow,\ Ice,\ Lava,\ Bog,\ HeavySand,\ Cloth,\ Glass,\ Bone,\ Rope,\ CharControl,\ Ragdoll,\ Surfing,\ GuardianFoot,\ HeavySnow,\ Unused0,\ LaunchPad,\ Conveyer,\ Rail,\ Grudge,\ Meat,\ Vegetable,\ Bomb,\ MagicBall,\ Barrier,\ AirWall,\ Misc,\ GrudgeSlow ) constexpr bool isInvalidMaterial(Material::ValueType mat) { return mat >= Material::size(); } SEAD_ENUM(GroundHit, Player,\ Animal,\ NPC,\ Camera,\ AttackHitPlayer,\ AttackHitEnemy,\ Arrow,\ Bomb,\ Magnet,\ CameraBody,\ IK,\ Grudge,\ MovingTrolley,\ LineOfSight,\ Giant,\ HitAll,\ Ignore ) SEAD_ENUM(FloorCode, None,\ Return,\ FlowStraight,\ FlowLeft,\ FlowRight,\ Slip,\ NarrowPlace,\ TopBroadleafTree,\ TopConiferousTree,\ Fall,\ Attach,\ NoImpulseUpperMove,\ NoPreventFall ) SEAD_ENUM(WallCode, None,\ NoClimb,\ Hang,\ LadderUp,\ Ladder,\ Slip,\ LadderSide,\ NoSlipRain,\ NoDashUpAndNoClimb,\ IceMakerBlock ) enum class MotionType { Dynamic = 0, Fixed = 1, Keyframed = 2, Unknown = 3, Invalid = -1, }; enum class GroundCollisionMode { /// Ground collision is not handled in any special way. Normal = 0, /// Any collision with a non-ground layer is ignored. IgnoreNonGround = 1, /// Any collision with a ground layer is ignored. IgnoreGround = 2, }; enum class WaterCollisionMode { /// Water collision is not handled in any special way. Normal = 0, /// Any collision with a water layer is ignored. IgnoreWater = 1, }; /// Collision filter info / collision mask that is used for entity rigid bodies. /// https://docs.google.com/spreadsheets/d/e/2PACX-1vQyEL5_Wee3MI23c-nHa4dMPJDVen9TMMcrOUX7Wka9NAH1AW9bkkq7ZJHawJkSzGOqgHUYc-83t4Or/pubhtml union EntityCollisionMask { union RegularMask { ContactLayer getLayer() const { return int(layer); } ContactLayer getLayerSensor() const { return int(layer + FirstSensor); } GroundHit getGroundHit() const { return int(ground_hit); } u32 raw; util::BitField<0, 5, u32> layer; /// Only valid for ragdoll masks. util::BitField<5, 5, u32> ragdoll_bone_index; /// Only valid for ragdoll masks. util::BitField<10, 5, u32> ragdoll_parent_bone_index; /// Only valid for CustomReceiver masks. /// Layers to collide with for EntityQueryCustomReceiver entities. util::BitField<5, NumRegularEntityLayers, u32> query_custom_receiver_layer_mask; /// Only valid for non-CustomReceiver masks. util::BitField<16, 10, u32> group_handler_index; util::BitField<26, 4, u32> ground_hit; }; union GroundHitMask { ContactLayer getLayer() const { return int(layer); } void addGroundHit(GroundHit hit) { raw |= (1 << hit) << decltype(ground_hit_types)::StartBit(); } u32 raw; util::BitField<0, 1, u32> unk; util::BitField<8, 16, u32> ground_hit_types; util::BitField<23, 1, u32> unk23; util::BitField<25, 5, u32> layer; }; constexpr explicit EntityCollisionMask(u32 raw_ = 0) : raw(raw_) {} constexpr EntityCollisionMask(const EntityCollisionMask&) = default; constexpr EntityCollisionMask& operator=(const EntityCollisionMask& m) { raw = m.raw; return *this; } bool operator==(EntityCollisionMask rhs) const { return raw == rhs.raw; } bool operator!=(EntityCollisionMask rhs) const { return raw != rhs.raw; } static EntityCollisionMask make(ContactLayer layer, GroundHit ground_hit) { EntityCollisionMask mask; mask.regular.layer.Init(layer); mask.regular.ground_hit.Init(ground_hit); return mask; } ContactLayer getLayer() const { return is_ground_hit_mask ? ground_hit.getLayer() : regular.getLayer(); } ContactLayer getLayerSensor() const { return is_ground_hit_mask ? ContactLayer(ContactLayer::SensorCustomReceiver) : regular.getLayerSensor(); } GroundHit getGroundHit() const { return is_ground_hit_mask ? GroundHit::HitAll : regular.getGroundHit(); } u32 raw; RegularMask regular; GroundHitMask ground_hit; /// Only valid in non-ragdoll, non-CustomReceiver mode. util::BitField<5, 2, GroundCollisionMode, u32> ground_col_mode; /// Only valid in non-ragdoll, non-CustomReceiver mode. util::BitField<7, 1, WaterCollisionMode, u32> water_col_mode; /// If this flag is set, then this entity will always collide with ground or water, /// regardless of the configured GroundCollisionMode or WaterCollisionMode modes. util::BitField<30, 1, bool, u32> is_ragdoll; util::BitField<31, 1, bool, u32> is_ground_hit_mask; }; static_assert(sizeof(EntityCollisionMask) == sizeof(u32)); /// Collision mask that is used for raycast-based queries against entities. union EntityQueryCollisionMask { constexpr explicit EntityQueryCollisionMask(u32 raw_ = 0) : raw(raw_) {} constexpr EntityQueryCollisionMask(const EntityQueryCollisionMask&) = default; constexpr EntityQueryCollisionMask& operator=(const EntityQueryCollisionMask& m) { raw = m.raw; return *this; } constexpr bool operator==(EntityQueryCollisionMask rhs) const { return raw == rhs.raw; } constexpr bool operator!=(EntityQueryCollisionMask rhs) const { return raw != rhs.raw; } util::BitField<0, NumRegularEntityLayers, u32> layer_mask; util::BitField<17, 1, u32> unk; util::BitField<18, 10, u32> group_handler_index; util::BitField<28, 4, GroundHit::ValueType, u32> ground_hit_type; u32 raw; }; union SensorCollisionMask { struct CustomReceiverTag {}; union Data { ContactLayer getLayer() const { return int(layer) + FirstSensor; } /// The sensor's contact layer. /// @note Add FirstSensor to get the actual ContactLayer value. util::BitField<0, 5, u32> layer; /// Whether `ignored_layer` contains a valid layer. util::BitField<5, 1, bool, u32> has_ignored_layer; /// Contact layer to ignore during collision detection. Only valid if `has_ignored_layer`. /// @note Add FirstSensor to get the actual ContactLayer value. util::BitField<6, 5, u32> ignored_layer; }; union CustomReceiverData { util::BitField<0, NumRegularSensorLayers, u32> layer; util::BitField<0, NumRegularSensorLayers, u32> layer_mask; }; constexpr SensorCollisionMask() : raw(0) {} constexpr explicit SensorCollisionMask(CustomReceiverTag) : raw(0) { is_custom_receiver = true; } constexpr explicit SensorCollisionMask(u32 raw_) : raw(raw_) {} constexpr SensorCollisionMask(const SensorCollisionMask&) = default; constexpr SensorCollisionMask& operator=(const SensorCollisionMask& m) { raw = m.raw; return *this; } static SensorCollisionMask make(ContactLayer layer) { SensorCollisionMask mask; if (layer == ContactLayer::SensorCustomReceiver) { mask.is_custom_receiver = true; mask.custom_receiver_data.layer.Init(ContactLayer::SensorCustomReceiver - FirstSensor); } else { mask.is_custom_receiver = false; mask.data.layer.Init(layer - FirstSensor); } return mask; } ContactLayer getLayer() const { return is_custom_receiver ? ContactLayer::SensorCustomReceiver : data.getLayer(); } u32 raw; Data data; CustomReceiverData custom_receiver_data; util::BitField<21, 10, u32> group_handler_index; util::BitField<31, 1, bool, u32> is_custom_receiver; }; /// Collision mask that is used for raycast-based queries against sensors. union SensorQueryCollisionMask { constexpr explicit SensorQueryCollisionMask(u32 raw_ = 0) : raw(raw_) {} constexpr SensorQueryCollisionMask(const SensorQueryCollisionMask&) = default; constexpr SensorQueryCollisionMask& operator=(const SensorQueryCollisionMask& m) { raw = m.raw; return *this; } constexpr bool operator==(SensorQueryCollisionMask rhs) const { return raw == rhs.raw; } constexpr bool operator!=(SensorQueryCollisionMask rhs) const { return raw != rhs.raw; } util::BitField<0, NumRegularSensorLayers, u32> layer_mask; util::BitField<22, 10, u32> group_handler_index; u32 raw; }; ContactLayerType getContactLayerType(ContactLayer layer); u32 makeContactLayerMask(ContactLayer layer); u32 getContactLayerBase(ContactLayerType type); int getContactLayerBaseRelativeValue(ContactLayer layer); const char* contactLayerToText(ContactLayer layer); ContactLayer contactLayerFromText(const sead::SafeString& text); const char* materialToText(Material material); Material materialFromText(const sead::SafeString& text); const char* groundHitToText(GroundHit hit); GroundHit groundHitFromText(const sead::SafeString& text); const char* floorCodeToText(FloorCode code); FloorCode floorCodeFromText(const sead::SafeString& text); const char* wallCodeToText(WallCode code); WallCode wallCodeFromText(const sead::SafeString& text); MotionType motionTypeFromText(const sead::SafeString& text); } // namespace ksys::phys