#include "hook_conditions.h" #include #include #include "gamesequence/gamesequence.h" #include "parse/sexp.h" #include "ship/ship.h" #include "weapon/weapon.h" #include "io/key.h" // ---- Hook Condition System Macro and Class defines ---- #define HOOK_CONDITIONS_START(name) const SCP_unordered_map> name::conditions = []() { \ SCP_unordered_map> build; #define HOOK_CONDITIONS_END return build; \ }(); #define HOOK_CONDITION(conditionsClassName, conditionParseName, documentation, argument, argumentParse, argumentValid) \ build.emplace(conditionParseName, std::make_unique().argument), decltype(argumentParse(std::declval()))>> \ (documentation, &conditionsClassName::argument, argumentParse, argumentValid)) extern const char *Scan_code_text_english[]; namespace scripting { template class EvaluatableConditionImpl; template class ParseableConditionImpl : public ParseableCondition { const operating_t conditions_t::* object; std::function cache; std::function evaluate; template friend class EvaluatableConditionImpl; public: std::unique_ptr parse(const SCP_string& input) const override { return std::make_unique>(*this, input); } ParseableConditionImpl(SCP_string documentation_, const operating_t conditions_t::* object_, std::function cache_, std::function evaluate_) : ParseableCondition(std::move(documentation_)), object(object_), cache(std::move(cache_)), evaluate(std::move(evaluate_)) { } }; template class EvaluatableConditionImpl : public EvaluatableCondition { const ParseableConditionImpl& condition; cache_t cached; public: EvaluatableConditionImpl(const ParseableConditionImpl& _condition, const SCP_string& input) : condition(_condition), cached(condition.cache(input)) { } bool evaluate(const std::any& conditionContext) const override { const conditions_t& conditions = std::any_cast(conditionContext); return condition.evaluate(conditions.*(condition.object), cached); } }; namespace hooks { // ---- Hook Condition System Utility and Parsing methods ---- static bool conditionCompareShip(const ship* shipp, const SCP_string& value) { return shipp != nullptr && stricmp(shipp->ship_name, value.c_str()) == 0; } static bool conditionCompareShipType(const ship* shipp, const int& value) { return shipp != nullptr && Ship_info[shipp->ship_info_index].class_type == value; } static bool conditionCompareShipClass(const ship* shipp, const int& value) { return shipp != nullptr && shipp->ship_info_index == value; } static bool conditionCompareWeaponClass(const weapon* wep, const int& value) { return wep != nullptr && wep->weapon_info_index == value; } static bool conditionIsObjecttype(const object* objp, const int& value) { return objp != nullptr && objp->type == value; } template static bool conditionObjectIsShipDo(fnc_t fnc, const object* objp, const value_t& value) { if (objp != nullptr && objp->type == OBJ_SHIP) { return fnc(&Ships[objp->instance], value); } return false; } template static bool conditionObjectIsWeaponDo(fnc_t fnc, const object* objp, const value_t& value) { if (objp != nullptr && objp->type == OBJ_WEAPON) { return fnc(&Weapons[objp->instance], value); } return false; } static int conditionCompareRawControl(int keypress, const int& cached_key) { //For reasons only known to Volition, LCtrl and RCtrl are differentiated in name, while Alt and Shift are not. //As only the first of these identical names will be matched, replace the R versions with the L versions int key_down = keypress & KEY_MASK; switch(key_down) { case KEY_RALT: key_down = KEY_LALT; break; case KEY_RSHIFT: key_down = KEY_LSHIFT; break; default: break; } return cached_key == key_down; } static SCP_string conditionParseString(const SCP_string& name) { return name; } static int conditionParseActionId(const SCP_string& name) { for (int i = 0; i < (int)Control_config.size(); i++) { if (lcase_equal(Control_config[i].text, name)) return i; } return -1; } static int conditionParseShipType(const SCP_string& name) { return ship_type_name_lookup(name.c_str()); } static int conditionParseShipClass(const SCP_string& name) { return ship_info_lookup(name.c_str()); } static int conditionParseWeaponClass(const SCP_string& name) { return weapon_info_lookup(name.c_str()); } static int conditionParseObjectType(const SCP_string& name) { for (int i = 0; i < MAX_OBJECT_TYPES; i++) { if (stricmp(Object_type_names[i], name.c_str()) == 0) return i; } return -1; } static int conditionParseRawControl(const SCP_string& name) { for (int key = 0; key < NUM_KEYS; key++){ if (stricmp(Scan_code_text_english[key], name.c_str()) == 0) return key & KEY_MASK; } return -1; } // ---- Hook Condition Helpers ---- #define HOOK_CONDITION_SHIPP(classname, prefix, documentationAddendum, shipp) \ HOOK_CONDITION(classname, prefix "Ship", "Specifies the name of the ship " documentationAddendum, shipp, conditionParseString, conditionCompareShip); \ HOOK_CONDITION(classname, prefix "Ship class", "Specifies the class of the ship " documentationAddendum, shipp, conditionParseShipClass, conditionCompareShipClass); \ HOOK_CONDITION(classname, prefix "Ship type", "Specifies the type of the ship " documentationAddendum, shipp, conditionParseShipType, conditionCompareShipType); #define HOOK_CONDITION_SHIP_OBJP(classname, prefix, documentationAddendum, objp_) \ HOOK_CONDITION(classname, prefix "Ship", "Specifies the name of the ship " documentationAddendum, objp_, conditionParseString, [](const object* objp, const SCP_string& shipname) -> bool { \ return conditionObjectIsShipDo(&conditionCompareShip, objp, shipname); \ }); \ HOOK_CONDITION(classname, prefix "Ship class", "Specifies the class of the ship " documentationAddendum, objp_, conditionParseShipClass, [](const object* objp, const int& shipclass) -> bool { \ return conditionObjectIsShipDo(&conditionCompareShipClass, objp, shipclass); \ }); \ HOOK_CONDITION(classname, prefix "Ship type", "Specifies the type of the ship " documentationAddendum, objp_, conditionParseShipType, [](const object* objp, const int& shiptype) -> bool { \ return conditionObjectIsShipDo(&conditionCompareShipType, objp, shiptype); \ }); // ---- Hook Conditions ---- HOOK_CONDITIONS_START(ControlActionConditions) HOOK_CONDITION(ControlActionConditions, "Action", "Specifies the action triggered by a keypress.", action_index, conditionParseActionId, [](int target, const int& key) -> bool { if (gameseq_get_depth() < 0) return false; return key == target; }); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ShipSourceConditions) HOOK_CONDITION_SHIPP(ShipSourceConditions, "", "that was the source of the event.", source_shipp); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(CollisionConditions) //These conditions with "must be one of the ships" instead of "must be both ships" is unlike the previous behaviour of PR #4231, but is closer to the original behaviour, where a specific ship of the two must match HOOK_CONDITION(CollisionConditions, "Ship", "Specifies the name of the ship which was part of the collision. At least one ship must be part of the collision and match.", participating_objects, conditionParseString, [](CollisionConditions::ParticipatingObjects po, const SCP_string& shipname) -> bool { if(conditionObjectIsShipDo(&conditionCompareShip, po.objp_a, shipname)) return true; if (conditionObjectIsShipDo(&conditionCompareShip, po.objp_b, shipname)) return true; return false; }); HOOK_CONDITION(CollisionConditions, "Ship class", "Specifies the class of the ship which was part of the collision. At least one ship must be part of the collision and match.", participating_objects, conditionParseShipClass, [](CollisionConditions::ParticipatingObjects po, const int& shipclass) -> bool { if (conditionObjectIsShipDo(&conditionCompareShipClass, po.objp_a, shipclass)) return true; if (conditionObjectIsShipDo(&conditionCompareShipClass, po.objp_b, shipclass)) return true; return false; }); HOOK_CONDITION(CollisionConditions, "Ship type", "Specifies the type of the ship which was part of the collision. At least one ship must be part of the collision and match.", participating_objects, conditionParseShipType, [](CollisionConditions::ParticipatingObjects po, const int& shiptype) -> bool { if (conditionObjectIsShipDo(&conditionCompareShipType, po.objp_a, shiptype)) return true; if (conditionObjectIsShipDo(&conditionCompareShipType, po.objp_b, shiptype)) return true; return false; }); HOOK_CONDITION(CollisionConditions, "Weapon class", "Specifies the name of the weapon class which was part of the collision. At least one weapon must be part of the collision and match.", participating_objects, conditionParseWeaponClass, [](CollisionConditions::ParticipatingObjects po, const int& weaponclass) -> bool { if (conditionObjectIsWeaponDo(&conditionCompareWeaponClass, po.objp_a, weaponclass)) return true; if (conditionObjectIsWeaponDo(&conditionCompareWeaponClass, po.objp_b, weaponclass)) return true; return false; }); HOOK_CONDITION(CollisionConditions, "Object type", "Specifies the type of the object which was part of the collision. At least one object must match.", participating_objects, conditionParseObjectType, [](CollisionConditions::ParticipatingObjects po, const int& objecttype) -> bool { if (conditionIsObjecttype(po.objp_a, objecttype)) return true; if (conditionIsObjecttype(po.objp_b, objecttype)) return true; return false; }); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ShipDeathConditions) HOOK_CONDITION_SHIPP(ShipDeathConditions, "", "that died.", dying_shipp); HOOK_CONDITIONS_END // Damage with no weapon has weapon_info_index -1, and so does a weapon name that does not exist, so a named weapon // class must also be a real weapon to match. static bool conditionDamageWeaponClassMatches(int damage_weapon_index, const int& weapon_index) { return damage_weapon_index >= 0 && damage_weapon_index == weapon_index; } HOOK_CONDITIONS_START(ShipDamageConditions) HOOK_CONDITION_SHIPP(ShipDamageConditions, "Target ", "that took damage.", target_shipp); HOOK_CONDITION(ShipDamageConditions, "Weapon class", "Specifies the class of the weapon that caused the damage. For a shockwave, this is the weapon that created it.", weapon_info_index, conditionParseWeaponClass, conditionDamageWeaponClassMatches); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(SubsystemDeathConditions) HOOK_CONDITION_SHIPP(SubsystemDeathConditions, "", "whose subsystem got destroyed.", affected_shipp); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ShipDepartConditions) HOOK_CONDITION_SHIPP(ShipDepartConditions, "", "that departed.", leaving_shipp); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponDeathConditions) HOOK_CONDITION(WeaponDeathConditions, "Weapon class", "Specifies the class of the weapon that died.", dying_wep, conditionParseWeaponClass, conditionCompareWeaponClass); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponProximityTriggeredConditions) HOOK_CONDITION(WeaponProximityTriggeredConditions, "Weapon class", "Specifies the class of the weapon that was triggered.", triggered_wep, conditionParseWeaponClass, conditionCompareWeaponClass); HOOK_CONDITION_SHIPP(WeaponProximityTriggeredConditions, "", "that triggered the weapon.", trigger_shipp); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ObjectDeathConditions) HOOK_CONDITION_SHIP_OBJP(ObjectDeathConditions, "", "that died.", dying_objp); HOOK_CONDITION(ObjectDeathConditions, "Weapon class", "Specifies the class of the weapon that died.", dying_objp, conditionParseWeaponClass, [](const object* objp, const int& weaponclass) -> bool { return conditionObjectIsWeaponDo(&conditionCompareWeaponClass, objp, weaponclass); }); HOOK_CONDITION(ObjectDeathConditions, "Object type", "Specifies the type of the object that died.", dying_objp, conditionParseObjectType, conditionIsObjecttype); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ShipArriveConditions) HOOK_CONDITION_SHIPP(ShipArriveConditions, "", "that arrived.", spawned_shipp); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponCreatedConditions) HOOK_CONDITION_SHIP_OBJP(WeaponCreatedConditions, "", "that fired the weapon.", parent_objp); HOOK_CONDITION(WeaponCreatedConditions, "Object type", "Specifies the type of the object that is the parent of this weapon.", parent_objp, conditionParseObjectType, conditionIsObjecttype); HOOK_CONDITION(WeaponCreatedConditions, "Weapon class", "Specifies the class of the weapon that was fired.", spawned_wep, conditionParseWeaponClass, conditionCompareWeaponClass); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponEquippedConditions) HOOK_CONDITION_SHIPP(WeaponEquippedConditions, "", "that has the weapon equipped.", user_shipp); HOOK_CONDITION(WeaponEquippedConditions, "Weapon class", "Specifies the class of the weapon that the ship needs to have equipped in at least one bank.", user_shipp, conditionParseWeaponClass, [](const ship* wielder, const int& weaponclass) -> bool { for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) { if (wielder->weapons.primary_bank_weapons[i] >= 0 && wielder->weapons.primary_bank_weapons[i] == weaponclass) return true; } for (int i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++) { if (wielder->weapons.secondary_bank_weapons[i] >= 0 && wielder->weapons.secondary_bank_weapons[i] == weaponclass) return true; } return false; }); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponUsedConditions) HOOK_CONDITION_SHIPP(WeaponUsedConditions, "", "that fired the weapon.", user_shipp); HOOK_CONDITION(WeaponUsedConditions, "Weapon class", "Specifies the class of the weapon that was fired.", weaponclasses, conditionParseWeaponClass, [](const SCP_vector& weaponclass_list, const int& weaponclass) -> bool { return std::count(weaponclass_list.cbegin(), weaponclass_list.cend(), weaponclass) > 0; }); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponSelectedConditions) HOOK_CONDITION_SHIPP(WeaponSelectedConditions, "", "that has selected the weapon.", user_shipp); HOOK_CONDITION(WeaponSelectedConditions, "Weapon class", "Specifies the class of the weapon that was selected.", weaponclass, conditionParseWeaponClass, std::equal_to()); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(WeaponDeselectedConditions) HOOK_CONDITION_SHIPP(WeaponDeselectedConditions, "", "that has deselected the weapon.", user_shipp); HOOK_CONDITION(WeaponDeselectedConditions, "Weapon class", "Specifies the class of the weapon that was deselected.", weaponclass_prev, conditionParseWeaponClass, std::equal_to()); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(ObjectDrawConditions) HOOK_CONDITION_SHIP_OBJP(ObjectDrawConditions, "", "that was drawn / drawn from.", drawn_from_objp); HOOK_CONDITION(ObjectDrawConditions, "Weapon class", "Specifies the class of the weapon that was drawn / drawn from.", drawn_from_objp, conditionParseWeaponClass, [](const object* objp, const int& weaponclass) -> bool { return conditionObjectIsWeaponDo(&conditionCompareWeaponClass, objp, weaponclass); }); HOOK_CONDITION(ObjectDrawConditions, "Object type", "Specifies the type of the object that was drawn / drawn from.", drawn_from_objp, conditionParseObjectType, conditionIsObjecttype); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(KeyPressConditions) HOOK_CONDITION(KeyPressConditions, "Raw KeyPress", "The key that is pressed, with no consideration for any modifier keys.", keycode, conditionParseRawControl, conditionCompareRawControl); HOOK_CONDITIONS_END HOOK_CONDITIONS_START(CommOrderConditions) HOOK_CONDITION_SHIPP(CommOrderConditions, "", "that sent the order.", source); HOOK_CONDITION(CommOrderConditions, "Object type", "Specifies the type of object that is the target of the order.", target, conditionParseObjectType, conditionIsObjecttype); HOOK_CONDITION_SHIP_OBJP(CommOrderConditions, "Target ", "that is being targeted.", target); HOOK_CONDITIONS_END } }