diff --git a/gamelink_single.hpp b/gamelink_single.hpp index 80cd4f1..66890b6 100644 --- a/gamelink_single.hpp +++ b/gamelink_single.hpp @@ -30304,7 +30304,8 @@ namespace gateway std::function OnComplete; }; - struct MatchPollConfiguration + template + struct GenericMatchPollConfiguration { string Prompt; std::vector Options; @@ -30318,7 +30319,7 @@ namespace gateway int32_t Duration = 0; // Arbitrary user data to send. Should be small. - nlohmann::json UserData; + T UserData; // Called regularly as poll results are streamed in from the server std::function OnUpdate; @@ -30327,6 +30328,44 @@ namespace gateway std::function OnComplete; }; + typedef GenericMatchPollConfiguration MatchPollConfiguration; + + struct GamechangerTier + { + string IncrementalText; + double IncrementalValue; + + string EffectText; + double EffectValue; + + // In seconds + int64_t TierDuration; + + int64_t TierThreshold; + + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_6(GamechangerTier, + "incremental_effect_text", IncrementalText, + "incremental_effect_value", IncrementalValue, + "effect_text", EffectText, + "effect_value", EffectValue, + "tier_duration", TierDuration, + "tier_threshold", TierThreshold + ); + }; + + struct GamechangerPollData + { + string Name; + std::vector Tiers; + + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_2(GamechangerPollData, + "name", Name, + "tiers", Tiers + ); + }; + + typedef GenericMatchPollConfiguration GamechangerMatchPollConfiguration; + enum class ActionCategory { Neutral = 0, @@ -30509,9 +30548,41 @@ namespace gateway void AddChannelsToMatch(const string& match, const string* start, const string* end); void RemoveChannelsFromMatch(const string& match, const string* start, const string* end); + template + void RunMatchPoll(const string& match, const GenericMatchPollConfiguration& cfg) + { + MatchPollConfiguration proxy; + proxy.Prompt = cfg.Prompt; + proxy.Options = cfg.Options; + proxy.Mode = cfg.Mode; + proxy.Location = cfg.Location; + proxy.Duration = cfg.Duration; + proxy.UserData = cfg.UserData; + proxy.OnUpdate = cfg.OnUpdate; + proxy.OnComplete = cfg.OnComplete; + + return RunMatchPoll(match, proxy); + } + void RunMatchPoll(const string& match, const MatchPollConfiguration& cfg); void StopMatchPoll(const string& match); + template + void RunMatchPollWithID(const string& match, const string& id, const GenericMatchPollConfiguration& cfg) + { + MatchPollConfiguration proxy; + proxy.Prompt = cfg.Prompt; + proxy.Options = cfg.Options; + proxy.Mode = cfg.Mode; + proxy.Location = cfg.Location; + proxy.Duration = cfg.Duration; + proxy.UserData = cfg.UserData; + proxy.OnUpdate = cfg.OnUpdate; + proxy.OnComplete = cfg.OnComplete; + + return RunMatchPollWithID(match, id, proxy); + } + void RunMatchPollWithID(const string& match, const string& id, const MatchPollConfiguration& cfg); void StopMatchPollWithID(const string& match, const string& id); private: diff --git a/include/gateway.h b/include/gateway.h index b64b289..65b1afc 100644 --- a/include/gateway.h +++ b/include/gateway.h @@ -122,7 +122,8 @@ namespace gateway std::function OnComplete; }; - struct MatchPollConfiguration + template + struct GenericMatchPollConfiguration { string Prompt; std::vector Options; @@ -136,7 +137,7 @@ namespace gateway int32_t Duration = 0; // Arbitrary user data to send. Should be small. - nlohmann::json UserData; + T UserData; // Called regularly as poll results are streamed in from the server std::function OnUpdate; @@ -145,6 +146,47 @@ namespace gateway std::function OnComplete; }; + typedef GenericMatchPollConfiguration MatchPollConfiguration; + + struct GamechangerTier + { + string IncrementalText; + double IncrementalValue; + + string EffectText; + double EffectValue; + + // In seconds + int64_t TierDuration; + + int64_t TierThreshold; + + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_6(GamechangerTier, + "incremental_effect_text", IncrementalText, + "incremental_effect_value", IncrementalValue, + "effect_text", EffectText, + "effect_value", EffectValue, + "tier_duration", TierDuration, + "tier_threshold", TierThreshold + ); + }; + + struct GamechangerPollData + { + string Name; + std::vector Tiers; + + // Shouldn't need to be changed, a marker to signal for special-case handling + string Type = string("gamechanger"); + + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_3(GamechangerPollData, + "name", Name, + "type", Type, + "tiers", Tiers + ); + }; + + typedef GenericMatchPollConfiguration GamechangerMatchPollConfiguration; enum class ActionCategory { @@ -328,9 +370,41 @@ namespace gateway void AddChannelsToMatch(const string& match, const string* start, const string* end); void RemoveChannelsFromMatch(const string& match, const string* start, const string* end); + template + void RunMatchPoll(const string& match, const GenericMatchPollConfiguration& cfg) + { + MatchPollConfiguration proxy; + proxy.Prompt = cfg.Prompt; + proxy.Options = cfg.Options; + proxy.Mode = cfg.Mode; + proxy.Location = cfg.Location; + proxy.Duration = cfg.Duration; + proxy.UserData = cfg.UserData; + proxy.OnUpdate = cfg.OnUpdate; + proxy.OnComplete = cfg.OnComplete; + + return RunMatchPoll(match, proxy); + } + void RunMatchPoll(const string& match, const MatchPollConfiguration& cfg); void StopMatchPoll(const string& match); + template + void RunMatchPollWithID(const string& match, const string& id, const GenericMatchPollConfiguration& cfg) + { + MatchPollConfiguration proxy; + proxy.Prompt = cfg.Prompt; + proxy.Options = cfg.Options; + proxy.Mode = cfg.Mode; + proxy.Location = cfg.Location; + proxy.Duration = cfg.Duration; + proxy.UserData = cfg.UserData; + proxy.OnUpdate = cfg.OnUpdate; + proxy.OnComplete = cfg.OnComplete; + + return RunMatchPollWithID(match, id, proxy); + } + void RunMatchPollWithID(const string& match, const string& id, const MatchPollConfiguration& cfg); void StopMatchPollWithID(const string& match, const string& id); private: diff --git a/test/gateway_matches.cpp b/test/gateway_matches.cpp new file mode 100644 index 0000000..11d66ff --- /dev/null +++ b/test/gateway_matches.cpp @@ -0,0 +1,176 @@ +#include "catch2/catch.hpp" +#include "util.h" + +#include "gateway.h" + + +TEST_CASE("Create a gamechanger", "[gateway][matches]") +{ + gateway::SDK sdk("This is a game"); + + gateway::GamechangerMatchPollConfiguration cfg; + cfg.Duration = 120; + cfg.Prompt = "Empower my snacks"; + cfg.Options = { + "Empower", + }; + + cfg.Mode = gateway::PollMode::Chaos; + cfg.UserData.Name = "Delivery Budget"; + cfg.UserData.Tiers = { + gateway::GamechangerTier{ + .IncrementalText = "Spend $1 more", + .IncrementalValue = 1, + + .EffectText = "Spend $10 more", + .EffectValue = 10, + + .TierDuration = 120, + .TierThreshold = 0 + }, + + gateway::GamechangerTier{ + .EffectText = "Spend $100 more", + .EffectValue = 100, + + .IncrementalText = "Spend $10 more", + .IncrementalValue = 10, + + .TierDuration = 60, + .TierThreshold = 200 + }, + + gateway::GamechangerTier{ + .EffectText = "Spend $200 more", + .EffectValue = 200, + + .IncrementalText = "Spend $20 more", + .IncrementalValue = 20, + + .TierDuration = 30, + .TierThreshold = 400 + }, + + gateway::GamechangerTier{ + .EffectText = "Go for broke!!", + .EffectValue = 1000, + + .IncrementalText = "Please no more :(", + .IncrementalValue = 5, + + .TierDuration = 15, + .TierThreshold = 1000 + }, + }; + + sdk.RunMatchPoll("my-cool-match", cfg); + REQUIRE(sdk.HasPayloads()); + + validateSinglePayload(sdk, R"({ + "action": "delete", + "params":{ + "request_id": 65535, + "target": "match_poll" + }, + "data": { + "id": "my-cool-match", + "poll_id": "default" + } + })"); + + const char* msg = R"({ + "meta": { + "action": "nothing", + "request_id": 1 + } + })"; + sdk.ReceiveMessage(msg, strlen(msg)); + + validateSinglePayload(sdk, R"({ + "action": "subscribe", + "data": { + "topic_id": "my-cool-match" + }, + "params": { + "request_id": 65535, + "target": "match_poll" + } + })"); + + const char* msg2 = R"({ + "meta": { + "action": "nothing", + "request_id": 2 + } + })"; + sdk.ReceiveMessage(msg2, strlen(msg2)); + + const char* expected = R"({ + "action": "create", + "data": { + "id": "my-cool-match", + "poll": { + "config": { + "disabled": false, + "distinctOptionsPerUser": 258, + "endsAt": 0, + "endsIn": 120, + "startsAt": 0, + "startsIn": 0, + "totalVotesPerUser": 1024, + "userIDVoting": true, + "votesPerOption": 1024 + }, + "options": [ + "Empower" + ], + "poll_id": "default", + "prompt": "Empower my snacks", + "user_data": { + "name": "Delivery Budget", + "type": "gamechanger", + "tiers": [ + { + "effect_text": "Spend $10 more", + "effect_value": 10.0, + "incremental_effect_text": "Spend $1 more", + "incremental_effect_value": 1.0, + "tier_duration": 120, + "tier_threshold": 0 + }, + { + "effect_text": "Spend $100 more", + "effect_value": 100.0, + "incremental_effect_text": "Spend $10 more", + "incremental_effect_value": 10.0, + "tier_duration": 60, + "tier_threshold": 200 + }, + { + "effect_text": "Spend $200 more", + "effect_value": 200.0, + "incremental_effect_text": "Spend $20 more", + "incremental_effect_value": 20.0, + "tier_duration": 30, + "tier_threshold": 400 + }, + { + "effect_text": "Go for broke!!", + "effect_value": 1000.0, + "incremental_effect_text": "Please no more :(", + "incremental_effect_value": 5.0, + "tier_duration": 15, + "tier_threshold": 1000 + } + ] + } + } + }, + "params": { + "request_id": 65535, + "target": "match_poll" + } + })"; + + validateSinglePayload(sdk, expected); +} diff --git a/test/util.h b/test/util.h index c3e20e5..fc9e62f 100644 --- a/test/util.h +++ b/test/util.h @@ -165,6 +165,24 @@ inline void validateSinglePayload(gateway::SDK& sdk, const std::string& p) REQUIRE(!sdk.HasPayloads()); } + +inline void validateMultiplePayloads(gateway::SDK& sdk, const std::vector& p) +{ + REQUIRE(sdk.HasPayloads()); + + uint32_t count = 0; + sdk.ForeachPayload([p, &count](const gateway::Payload* payload) { + REQUIRE(count < p.size()); + + ConstrainedString str(reinterpret_cast(payload->GetData())); + REQUIRE(JSONEquals(str, ConstrainedString(p[count].c_str()))); + count++; + }); + + REQUIRE(count == p.size()); + REQUIRE(!sdk.HasPayloads()); +} + inline void validateSinglePayload(MGW_SDK sdk, const std::string& p) { gateway::SDK* SDK = static_cast(sdk.SDK);