Public Access
feat(rewards): add completed, background and banner effect items
This commit is contained in:
1 parent
349fa40272
commit
a12ed20705
7 files changed
+42
-8
No files matched your search
@@ -63,7 +63,8 @@ To Be Played is split into three repositories:
|
|||||||
snapshots, once per user, kind and game (`ref`). Rules and numbers: `RewardRules`; a Steam finish needs playtime and one
|
snapshots, once per user, kind and game (`ref`). Rules and numbers: `RewardRules`; a Steam finish needs playtime and one
|
||||||
proof of fresh play (ending achievement or playtime around the finish, or half of the IGDB time played since the finish; old history pays nothing). The month bonus needs a finished game in that month. Games added by hand pay 15 for
|
proof of fresh play (ending achievement or playtime around the finish, or half of the IGDB time played since the finish; old history pays nothing). The month bonus needs a finished game in that month. Games added by hand pay 15 for
|
||||||
a finish, at most 3 per ISO week. The settle takes a per-user advisory lock. V26 also pays a one-time starter grant
|
a finish, at most 3 per ISO week. The settle takes a per-user advisory lock. V26 also pays a one-time starter grant
|
||||||
for finishes from before the status log. Shop: a whitelist of 8 looks (`ShopItem`); a `BUY` row proves ownership,
|
for finishes from before the status log. Shop: a whitelist of 28 looks (`ShopItem`) in seven slots, including animated completed effects, page backgrounds and banner
|
||||||
|
backgrounds (V27 widens `reward_equipped.slot`); a `BUY` row proves ownership,
|
||||||
`reward_equipped` holds what is worn. A platform admin owns every item (the role of the login token, or the `role`
|
`reward_equipped` holds what is worn. A platform admin owns every item (the role of the login token, or the `role`
|
||||||
claim of the profile ticket for visitors).
|
claim of the profile ticket for visitors).
|
||||||
- **Wishlist**: synced from Steam with store price and discount.
|
- **Wishlist**: synced from Steam with store price and discount.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package core_service.modules.rewards.domain;
|
package core_service.modules.rewards.domain;
|
||||||
|
|
||||||
public enum LookSlot {
|
public enum LookSlot {
|
||||||
AVATAR_BORDER, BADGE, ACCENT, BANNER_FRAME
|
AVATAR_BORDER, BADGE, ACCENT, BANNER_FRAME, COMPLETED_EFFECT, PAGE_BACKGROUND, BANNER_BACKGROUND
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,8 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The whitelist of looks. The frontend maps a code to its visuals; nothing here is ever uploaded CSS.
|
* The whitelist of looks. The frontend maps a code to its visuals; nothing here is ever uploaded CSS.
|
||||||
|
* {@code FX_} effects show on the cover of a COMPLETED game and on its details page, {@code BG_} on the page
|
||||||
|
* background, {@code BN_} on the dashboard and profile banner.
|
||||||
*/
|
*/
|
||||||
public enum ShopItem {
|
public enum ShopItem {
|
||||||
BORDER_GOLD(LookSlot.AVATAR_BORDER, 150),
|
BORDER_GOLD(LookSlot.AVATAR_BORDER, 150),
|
||||||
@@ -15,7 +17,27 @@ public enum ShopItem {
|
|||||||
ACCENT_GOLD(LookSlot.ACCENT, 200),
|
ACCENT_GOLD(LookSlot.ACCENT, 200),
|
||||||
ACCENT_TEAL(LookSlot.ACCENT, 200),
|
ACCENT_TEAL(LookSlot.ACCENT, 200),
|
||||||
FRAME_GOLD(LookSlot.BANNER_FRAME, 150),
|
FRAME_GOLD(LookSlot.BANNER_FRAME, 150),
|
||||||
FRAME_NEON(LookSlot.BANNER_FRAME, 200);
|
FRAME_NEON(LookSlot.BANNER_FRAME, 200),
|
||||||
|
FX_GOLD_SHINE(LookSlot.COMPLETED_EFFECT, 300),
|
||||||
|
FX_NEON_PULSE(LookSlot.COMPLETED_EFFECT, 300),
|
||||||
|
FX_HOLO(LookSlot.COMPLETED_EFFECT, 350),
|
||||||
|
FX_EMBER(LookSlot.COMPLETED_EFFECT, 300),
|
||||||
|
BG_TRIANGLES(LookSlot.PAGE_BACKGROUND, 400),
|
||||||
|
BG_STARFIELD(LookSlot.PAGE_BACKGROUND, 400),
|
||||||
|
BG_PIXELS(LookSlot.PAGE_BACKGROUND, 400),
|
||||||
|
BG_AURORA(LookSlot.PAGE_BACKGROUND, 450),
|
||||||
|
BG_MESH(LookSlot.PAGE_BACKGROUND, 450),
|
||||||
|
BG_GRID(LookSlot.PAGE_BACKGROUND, 450),
|
||||||
|
BG_WAVES(LookSlot.PAGE_BACKGROUND, 400),
|
||||||
|
BG_BOKEH(LookSlot.PAGE_BACKGROUND, 400),
|
||||||
|
BN_TRIANGLES(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_STARFIELD(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_PIXELS(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_AURORA(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_MESH(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_GRID(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_WAVES(LookSlot.BANNER_BACKGROUND, 300),
|
||||||
|
BN_BOKEH(LookSlot.BANNER_BACKGROUND, 300);
|
||||||
|
|
||||||
private final LookSlot slot;
|
private final LookSlot slot;
|
||||||
private final int price;
|
private final int price;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- BANNER_BACKGROUND is 17 characters.
|
||||||
|
ALTER TABLE reward_equipped ALTER COLUMN slot TYPE VARCHAR(32);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package core_service.modules.rewards.api;
|
package core_service.modules.rewards.api;
|
||||||
|
|
||||||
import core_service.configuration.security.ClientHeaderFilter;
|
import core_service.configuration.security.ClientHeaderFilter;
|
||||||
|
import core_service.modules.rewards.domain.ShopItem;
|
||||||
import core_service.modules.rewards.service.RewardService;
|
import core_service.modules.rewards.service.RewardService;
|
||||||
import core_service.shared.security.Role;
|
import core_service.shared.security.Role;
|
||||||
import core_service.shared.test.AbstractIntegrationTest;
|
import core_service.shared.test.AbstractIntegrationTest;
|
||||||
@@ -81,7 +82,7 @@ class RewardControllerTest extends AbstractIntegrationTest {
|
|||||||
.andExpect(jsonPath("$.balance").value(30))
|
.andExpect(jsonPath("$.balance").value(30))
|
||||||
.andExpect(jsonPath("$.history[0].kind").value("FINISHED"))
|
.andExpect(jsonPath("$.history[0].kind").value("FINISHED"))
|
||||||
.andExpect(jsonPath("$.history[0].gameName").value("Game " + GAME))
|
.andExpect(jsonPath("$.history[0].gameName").value("Game " + GAME))
|
||||||
.andExpect(jsonPath("$.catalog.length()").value(8))
|
.andExpect(jsonPath("$.catalog.length()").value(ShopItem.values().length))
|
||||||
.andExpect(jsonPath("$.owned").isEmpty());
|
.andExpect(jsonPath("$.owned").isEmpty());
|
||||||
|
|
||||||
setStatus(GAME, "BACKLOG");
|
setStatus(GAME, "BACKLOG");
|
||||||
@@ -212,7 +213,7 @@ class RewardControllerTest extends AbstractIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
void admin_OwnsEveryItem_NeverBuysAndFallsBackToDefaultAsAUser() throws Exception {
|
void admin_OwnsEveryItem_NeverBuysAndFallsBackToDefaultAsAUser() throws Exception {
|
||||||
mockMvc.perform(asAdmin(get("/api/rewards/wallet")))
|
mockMvc.perform(asAdmin(get("/api/rewards/wallet")))
|
||||||
.andExpect(jsonPath("$.owned.length()").value(8));
|
.andExpect(jsonPath("$.owned.length()").value(ShopItem.values().length));
|
||||||
mockMvc.perform(asAdmin(post("/api/rewards/purchases")).header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
mockMvc.perform(asAdmin(post("/api/rewards/purchases")).header(ClientHeaderFilter.CLIENT_HEADER, "test")
|
||||||
.contentType(MediaType.APPLICATION_JSON).content("{\"item\":\"FRAME_GOLD\"}"))
|
.contentType(MediaType.APPLICATION_JSON).content("{\"item\":\"FRAME_GOLD\"}"))
|
||||||
.andExpect(status().isConflict()).andExpect(jsonPath("$.code").value("ALREADY_OWNED"));
|
.andExpect(status().isConflict()).andExpect(jsonPath("$.code").value("ALREADY_OWNED"));
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ class LooksTest extends AbstractUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
void wearable_DropsLooksThatAreNotOwnedOrNotInTheCatalog() {
|
void wearable_DropsLooksThatAreNotOwnedOrNotInTheCatalog() {
|
||||||
Map<LookSlot, String> equipped = Map.of(LookSlot.AVATAR_BORDER, "BORDER_GOLD", LookSlot.BADGE, "BADGE_PILE_SLAYER",
|
Map<LookSlot, String> equipped = Map.of(LookSlot.AVATAR_BORDER, "BORDER_GOLD", LookSlot.BADGE, "BADGE_PILE_SLAYER",
|
||||||
LookSlot.ACCENT, "ACCENT_NEON_PINK");
|
LookSlot.ACCENT, "ACCENT_NEON_PINK", LookSlot.BANNER_BACKGROUND, "BN_GRID");
|
||||||
|
|
||||||
assertThat(Looks.wearable(equipped, Set.of("BORDER_GOLD", "ACCENT_NEON_PINK")))
|
assertThat(Looks.wearable(equipped, Set.of("BORDER_GOLD", "ACCENT_NEON_PINK", "BN_GRID")))
|
||||||
.containsOnly(Map.entry(LookSlot.AVATAR_BORDER, "BORDER_GOLD"));
|
.containsOnly(Map.entry(LookSlot.AVATAR_BORDER, "BORDER_GOLD"),
|
||||||
|
Map.entry(LookSlot.BANNER_BACKGROUND, "BN_GRID"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,4 +99,11 @@ class RewardLedgerRepositoryTest extends AbstractPersistenceTest {
|
|||||||
|
|
||||||
assertThat(repository.equipped(USER)).containsOnlyKeys(LookSlot.ACCENT);
|
assertThat(repository.equipped(USER)).containsOnlyKeys(LookSlot.ACCENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void equip_TheLongestSlotNameFits() {
|
||||||
|
repository.equip(USER, LookSlot.BANNER_BACKGROUND, "BN_GRID");
|
||||||
|
|
||||||
|
assertThat(repository.equipped(USER)).containsOnly(Map.entry(LookSlot.BANNER_BACKGROUND, "BN_GRID"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user