diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 1f1ad60f9a..83fc4a22f3 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,6 +1,7 @@ 0.4.4 (in development) ------------------------------------------------------------------------ - Feature: [#18537] Add shift/control modifiers to window close buttons, closing all but the given window or all windows of the same type, respectively. +- Feature: [#18732] [Plugin] API to get the guests thoughts. - Feature: [#18744] Cheat to allow using a regular path as a queue path. - Improved: [#18826] [Plugin] Added all actions and their documentation to plugin API. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index bd2d29dac9..fdefb52a7e 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2493,8 +2493,173 @@ declare global { * Countdown between 0 and 255 that keeps track of how long the guest has been looking for its current destination. */ lostCountdown: number; + + /** + * The list of thoughts this guest has. + */ + readonly thoughts: Thought[]; } + /** + * Represents a guest's thought. This is a copy of a thought at a given time + * and its values will not update. + */ + interface Thought { + /** + * The type of thought. + */ + readonly type: ThoughtType; + + /** + * The thought argument. + */ + readonly item: number; + + /** + * The freshness of the thought - the larger the number, the less fresh + * the thought. + */ + readonly freshness: number; + + /** + * The freshness timeout. + */ + readonly freshTimeout: number; + + /** + * Get the string for this thought. + * The result contains Unicode quotes on either end, e.g. “I feel sick” + */ + toString(): string; + } + + type ThoughtType = + "cant_afford_ride" | + "spent_money" | + "sick" | + "very_sick" | + "more_thrilling" | + "intense" | + "havent_finished" | + "sickening" | + "bad_value" | + "go_home" | + "good_value" | + "already_got" | + "cant_afford_item" | + "not_hungry" | + "not_thirsty" | + "drowning" | + "lost" | + "was_great" | + "queuing_ages" | + "tired" | + "hungry" | + "thirsty" | + "toilet" | + "cant_find" | + "not_paying" | + "not_while_raining" | + "bad_litter" | + "cant_find_exit" | + "get_off" | + "get_out" | + "not_safe" | + "path_disgusting" | + "crowded" | + "vandalism" | + "scenery" | + "very_clean" | + "fountains" | + "music" | + "balloon" | + "toy" | + "map" | + "photo" | + "umbrella" | + "drink" | + "burger" | + "chips" | + "ice_cream" | + "candyfloss" | + "pizza" | + "popcorn" | + "hot_dog" | + "tentacle" | + "hat" | + "toffee_apple" | + "tshirt" | + "doughnut" | + "coffee" | + "chicken" | + "lemonade" | + "wow" | + "wow2" | + "watched" | + "balloon_much" | + "toy_much" | + "map_much" | + "photo_much" | + "umbrella_much" | + "drink_much" | + "burger_much" | + "chips_much" | + "ice_cream_much" | + "candyfloss_much" | + "pizza_much" | + "popcorn_much" | + "hot_dog_much" | + "tentacle_much" | + "hat_much" | + "toffee_apple_much" | + "tshirt_much" | + "doughnut_much" | + "coffee_much" | + "chicken_much" | + "lemonade_much" | + "photo2" | + "photo3" | + "photo4" | + "pretzel" | + "hot_chocolate" | + "iced_tea" | + "funnel_cake" | + "sunglasses" | + "beef_noodles" | + "fried_rice_noodles" | + "wonton_soup" | + "meatball_soup" | + "fruit_juice" | + "soybean_milk" | + "sujongkwa" | + "sub_sandwich" | + "cookie" | + "roast_sausage" | + "photo2_much" | + "photo3_much" | + "photo4_much" | + "pretzel_much" | + "hot_chocolate_much" | + "iced_tea_much" | + "funnel_cake_much" | + "sunglasses_much" | + "beef_noodles_much" | + "fried_rice_noodles_much" | + "wonton_soup_much" | + "meatball_soup_much" | + "fruit_juice_much" | + "soybean_milk_much" | + "sujongkwa_much" | + "sub_sandwich_much" | + "cookie_much" | + "roast_sausage_much" | + "help" | + "running_out" | + "new_ride" | + "nice_ride_deprecated" | + "excited_deprecated" | + "here_we_are"; + /** * Represents a staff member. */ diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 014ab7747c..d6978615be 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -422,6 +422,7 @@ void ScriptEngine::Initialise() ScVehicle::Register(ctx); ScPeep::Register(ctx); ScGuest::Register(ctx); + ScThought::Register(ctx); # ifndef DISABLE_NETWORK ScSocket::Register(ctx); ScListener::Register(ctx); diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 301c7f7ee5..dec34cfc66 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -46,7 +46,7 @@ namespace OpenRCT2 namespace OpenRCT2::Scripting { - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 66; + static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 67; // Versions marking breaking changes. static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; diff --git a/src/openrct2/scripting/bindings/entity/ScGuest.cpp b/src/openrct2/scripting/bindings/entity/ScGuest.cpp index 71c08b49eb..26dc6f5dea 100644 --- a/src/openrct2/scripting/bindings/entity/ScGuest.cpp +++ b/src/openrct2/scripting/bindings/entity/ScGuest.cpp @@ -12,9 +12,138 @@ # include "ScGuest.hpp" # include "../../../entity/Guest.h" +# include "../../../localisation/Localisation.h" namespace OpenRCT2::Scripting { + static const DukEnumMap ThoughtTypeMap({ + { "cant_afford_ride", PeepThoughtType::CantAffordRide }, + { "spent_money", PeepThoughtType::SpentMoney }, + { "sick", PeepThoughtType::Sick }, + { "very_sick", PeepThoughtType::VerySick }, + { "more_thrilling", PeepThoughtType::MoreThrilling }, + { "intense", PeepThoughtType::Intense }, + { "havent_finished", PeepThoughtType::HaventFinished }, + { "sickening", PeepThoughtType::Sickening }, + { "bad_value", PeepThoughtType::BadValue }, + { "go_home", PeepThoughtType::GoHome }, + { "good_value", PeepThoughtType::GoodValue }, + { "already_got", PeepThoughtType::AlreadyGot }, + { "cant_afford_item", PeepThoughtType::CantAffordItem }, + { "not_hungry", PeepThoughtType::NotHungry }, + { "not_thirsty", PeepThoughtType::NotThirsty }, + { "drowning", PeepThoughtType::Drowning }, + { "lost", PeepThoughtType::Lost }, + { "was_great", PeepThoughtType::WasGreat }, + { "queuing_ages", PeepThoughtType::QueuingAges }, + { "tired", PeepThoughtType::Tired }, + { "hungry", PeepThoughtType::Hungry }, + { "thirsty", PeepThoughtType::Thirsty }, + { "toilet", PeepThoughtType::Toilet }, + { "cant_find", PeepThoughtType::CantFind }, + { "not_paying", PeepThoughtType::NotPaying }, + { "not_while_raining", PeepThoughtType::NotWhileRaining }, + { "bad_litter", PeepThoughtType::BadLitter }, + { "cant_find_exit", PeepThoughtType::CantFindExit }, + { "get_off", PeepThoughtType::GetOff }, + { "get_out", PeepThoughtType::GetOut }, + { "not_safe", PeepThoughtType::NotSafe }, + { "path_disgusting", PeepThoughtType::PathDisgusting }, + { "crowded", PeepThoughtType::Crowded }, + { "vandalism", PeepThoughtType::Vandalism }, + { "scenery", PeepThoughtType::Scenery }, + { "very_clean", PeepThoughtType::VeryClean }, + { "fountains", PeepThoughtType::Fountains }, + { "music", PeepThoughtType::Music }, + { "balloon", PeepThoughtType::Balloon }, + { "toy", PeepThoughtType::Toy }, + { "map", PeepThoughtType::Map }, + { "photo", PeepThoughtType::Photo }, + { "umbrella", PeepThoughtType::Umbrella }, + { "drink", PeepThoughtType::Drink }, + { "burger", PeepThoughtType::Burger }, + { "chips", PeepThoughtType::Chips }, + { "ice_cream", PeepThoughtType::IceCream }, + { "candyfloss", PeepThoughtType::Candyfloss }, + { "pizza", PeepThoughtType::Pizza }, + { "popcorn", PeepThoughtType::Popcorn }, + { "hot_dog", PeepThoughtType::HotDog }, + { "tentacle", PeepThoughtType::Tentacle }, + { "hat", PeepThoughtType::Hat }, + { "toffee_apple", PeepThoughtType::ToffeeApple }, + { "tshirt", PeepThoughtType::Tshirt }, + { "doughnut", PeepThoughtType::Doughnut }, + { "coffee", PeepThoughtType::Coffee }, + { "chicken", PeepThoughtType::Chicken }, + { "lemonade", PeepThoughtType::Lemonade }, + { "wow", PeepThoughtType::Wow }, + { "wow2", PeepThoughtType::Wow2 }, + { "watched", PeepThoughtType::Watched }, + { "balloon_much", PeepThoughtType::BalloonMuch }, + { "toy_much", PeepThoughtType::ToyMuch }, + { "map_much", PeepThoughtType::MapMuch }, + { "photo_much", PeepThoughtType::PhotoMuch }, + { "umbrella_much", PeepThoughtType::UmbrellaMuch }, + { "drink_much", PeepThoughtType::DrinkMuch }, + { "burger_much", PeepThoughtType::BurgerMuch }, + { "chips_much", PeepThoughtType::ChipsMuch }, + { "ice_cream_much", PeepThoughtType::IceCreamMuch }, + { "candyfloss_much", PeepThoughtType::CandyflossMuch }, + { "pizza_much", PeepThoughtType::PizzaMuch }, + { "popcorn_much", PeepThoughtType::PopcornMuch }, + { "hot_dog_much", PeepThoughtType::HotDogMuch }, + { "tentacle_much", PeepThoughtType::TentacleMuch }, + { "hat_much", PeepThoughtType::HatMuch }, + { "toffee_apple_much", PeepThoughtType::ToffeeAppleMuch }, + { "tshirt_much", PeepThoughtType::TshirtMuch }, + { "doughnut_much", PeepThoughtType::DoughnutMuch }, + { "coffee_much", PeepThoughtType::CoffeeMuch }, + { "chicken_much", PeepThoughtType::ChickenMuch }, + { "lemonade_much", PeepThoughtType::LemonadeMuch }, + { "photo2", PeepThoughtType::Photo2 }, + { "photo3", PeepThoughtType::Photo3 }, + { "photo4", PeepThoughtType::Photo4 }, + { "pretzel", PeepThoughtType::Pretzel }, + { "hot_chocolate", PeepThoughtType::HotChocolate }, + { "iced_tea", PeepThoughtType::IcedTea }, + { "funnel_cake", PeepThoughtType::FunnelCake }, + { "sunglasses", PeepThoughtType::Sunglasses }, + { "beef_noodles", PeepThoughtType::BeefNoodles }, + { "fried_rice_noodles", PeepThoughtType::FriedRiceNoodles }, + { "wonton_soup", PeepThoughtType::WontonSoup }, + { "meatball_soup", PeepThoughtType::MeatballSoup }, + { "fruit_juice", PeepThoughtType::FruitJuice }, + { "soybean_milk", PeepThoughtType::SoybeanMilk }, + { "sujongkwa", PeepThoughtType::Sujongkwa }, + { "sub_sandwich", PeepThoughtType::SubSandwich }, + { "cookie", PeepThoughtType::Cookie }, + { "roast_sausage", PeepThoughtType::RoastSausage }, + { "photo2_much", PeepThoughtType::Photo2Much }, + { "photo3_much", PeepThoughtType::Photo3Much }, + { "photo4_much", PeepThoughtType::Photo4Much }, + { "pretzel_much", PeepThoughtType::PretzelMuch }, + { "hot_chocolate_much", PeepThoughtType::HotChocolateMuch }, + { "iced_tea_much", PeepThoughtType::IcedTeaMuch }, + { "funnel_cake_much", PeepThoughtType::FunnelCakeMuch }, + { "sunglasses_much", PeepThoughtType::SunglassesMuch }, + { "beef_noodles_much", PeepThoughtType::BeefNoodlesMuch }, + { "fried_rice_noodles_much", PeepThoughtType::FriedRiceNoodlesMuch }, + { "wonton_soup_much", PeepThoughtType::WontonSoupMuch }, + { "meatball_soup_much", PeepThoughtType::MeatballSoupMuch }, + { "fruit_juice_much", PeepThoughtType::FruitJuiceMuch }, + { "soybean_milk_much", PeepThoughtType::SoybeanMilkMuch }, + { "sujongkwa_much", PeepThoughtType::SujongkwaMuch }, + { "sub_sandwich_much", PeepThoughtType::SubSandwichMuch }, + { "cookie_much", PeepThoughtType::CookieMuch }, + { "roast_sausage_much", PeepThoughtType::RoastSausageMuch }, + { "help", PeepThoughtType::Help }, + { "running_out", PeepThoughtType::RunningOut }, + { "new_ride", PeepThoughtType::NewRide }, + { "nice_ride_deprecated", PeepThoughtType::NiceRideDeprecated }, + { "excited_deprecated", PeepThoughtType::ExcitedDeprecated }, + { "here_we_are", PeepThoughtType::HereWeAre }, + }); + ScGuest::ScGuest(EntityId id) : ScPeep(id) { @@ -43,6 +172,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScGuest::isInPark_get, nullptr, "isInPark"); dukglue_register_property(ctx, &ScGuest::isLost_get, nullptr, "isLost"); dukglue_register_property(ctx, &ScGuest::lostCountdown_get, &ScGuest::lostCountdown_set, "lostCountdown"); + dukglue_register_property(ctx, &ScGuest::thoughts_get, nullptr, "thoughts"); } Guest* ScGuest::GetGuest() const @@ -337,6 +467,75 @@ namespace OpenRCT2::Scripting } } + DukValue ScGuest::thoughts_get() const + { + auto ctx = GetContext()->GetScriptEngine().GetContext(); + + duk_push_array(ctx); + + auto peep = GetGuest(); + if (peep != nullptr) + { + duk_uarridx_t index = 0; + for (const auto& thought : peep->Thoughts) + { + if (thought.type == PeepThoughtType::None) + break; + if (thought.freshness == 0) + continue; + auto scThoughtPtr = std::make_shared(thought); + auto dukThought = GetObjectAsDukValue(ctx, scThoughtPtr); + dukThought.push(); + duk_put_prop_index(ctx, -2, index); + index++; + } + } + + return DukValue::take_from_stack(ctx, -1); + } + + ScThought::ScThought(PeepThought backing) + : _backing(backing) + { + } + + void ScThought::Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScThought::type_get, nullptr, "type"); + dukglue_register_property(ctx, &ScThought::item_get, nullptr, "item"); + dukglue_register_property(ctx, &ScThought::freshness_get, nullptr, "freshness"); + dukglue_register_property(ctx, &ScThought::freshTimeout_get, nullptr, "freshTimeout"); + dukglue_register_method(ctx, &ScThought::toString, "toString"); + } + + std::string ScThought::type_get() const + { + return std::string(ThoughtTypeMap[_backing.type]); + } + + uint16_t ScThought::item_get() const + { + return _backing.item; + } + + uint8_t ScThought::freshness_get() const + { + return _backing.freshness; + } + + uint8_t ScThought::freshTimeout_get() const + { + return _backing.fresh_timeout; + } + + std::string ScThought::toString() const + { + // format string with arguments + auto ft = Formatter(); + peep_thought_set_format_args(&_backing, ft); + return format_string(STR_STRINGID, ft.Data()); + } + } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2/scripting/bindings/entity/ScGuest.hpp b/src/openrct2/scripting/bindings/entity/ScGuest.hpp index 06dfcbbc1d..b7ac5fb0aa 100644 --- a/src/openrct2/scripting/bindings/entity/ScGuest.hpp +++ b/src/openrct2/scripting/bindings/entity/ScGuest.hpp @@ -11,10 +11,29 @@ #ifdef ENABLE_SCRIPTING +# include "../../../entity/Guest.h" # include "ScPeep.hpp" namespace OpenRCT2::Scripting { + class ScThought + { + private: + PeepThought _backing; + + public: + ScThought(PeepThought backing); + + static void Register(duk_context* ctx); + + private: + std::string type_get() const; + uint16_t item_get() const; + uint8_t freshness_get() const; + uint8_t freshTimeout_get() const; + std::string toString() const; + }; + class ScGuest : public ScPeep { public: @@ -82,6 +101,8 @@ namespace OpenRCT2::Scripting uint8_t lostCountdown_get() const; void lostCountdown_set(uint8_t value); + + DukValue thoughts_get() const; }; } // namespace OpenRCT2::Scripting